diff --git a/example_brep/example_brep.xcodeproj/project.pbxproj b/example_brep/example_brep.xcodeproj/project.pbxproj index f681238a..8879fd1d 100644 --- a/example_brep/example_brep.xcodeproj/project.pbxproj +++ b/example_brep/example_brep.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 1D41D1AE1EE090BE00EB94A6 /* example_brep.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D41D1AD1EE090BE00EB94A6 /* example_brep.cpp */; }; 1D41D1C61EE09FDE00EB94A6 /* libopennurbs_public.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1C51EE09FDE00EB94A6 /* libopennurbs_public.a */; }; - 1D41D1C81EE09FE500EB94A6 /* libopennurbs_public_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1C71EE09FE500EB94A6 /* libopennurbs_public_freetype.a */; }; 1D41D1CA1EE09FEB00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1C91EE09FEB00EB94A6 /* libopennurbs_public_zlib.a */; }; 1D77E38D1EE20FC100994B0B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D77E38C1EE20FC100994B0B /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -30,7 +29,6 @@ 1D41D17D1EE08F2B00EB94A6 /* example_brep */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = example_brep; sourceTree = BUILT_PRODUCTS_DIR; }; 1D41D1AD1EE090BE00EB94A6 /* example_brep.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = example_brep.cpp; sourceTree = ""; }; 1D41D1C51EE09FDE00EB94A6 /* libopennurbs_public.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public.a; path = ../build/Debug/libopennurbs_public.a; sourceTree = ""; }; - 1D41D1C71EE09FE500EB94A6 /* libopennurbs_public_freetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_freetype.a; path = ../freetype263/build/Debug/libopennurbs_public_freetype.a; sourceTree = ""; }; 1D41D1C91EE09FEB00EB94A6 /* libopennurbs_public_zlib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_zlib.a; path = ../zlib/build/Debug/libopennurbs_public_zlib.a; sourceTree = ""; }; 1D77E38C1EE20FC100994B0B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -42,7 +40,6 @@ files = ( 1D77E38D1EE20FC100994B0B /* Cocoa.framework in Frameworks */, 1D41D1CA1EE09FEB00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */, - 1D41D1C81EE09FE500EB94A6 /* libopennurbs_public_freetype.a in Frameworks */, 1D41D1C61EE09FDE00EB94A6 /* libopennurbs_public.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -72,7 +69,6 @@ children = ( 1D77E38C1EE20FC100994B0B /* Cocoa.framework */, 1D41D1C91EE09FEB00EB94A6 /* libopennurbs_public_zlib.a */, - 1D41D1C71EE09FE500EB94A6 /* libopennurbs_public_freetype.a */, 1D41D1C51EE09FDE00EB94A6 /* libopennurbs_public.a */, ); name = Frameworks; diff --git a/example_brep/my_brep.3dm b/example_brep/my_brep.3dm new file mode 100644 index 00000000..4701cd66 Binary files /dev/null and b/example_brep/my_brep.3dm differ diff --git a/example_read/example_read.xcodeproj/project.pbxproj b/example_read/example_read.xcodeproj/project.pbxproj index 33c4c1ce..d6173e11 100644 --- a/example_read/example_read.xcodeproj/project.pbxproj +++ b/example_read/example_read.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 1DCB16F11EE0835C004CC693 /* example_read.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DCB16F01EE0835C004CC693 /* example_read.cpp */; }; 1DCB16F41EE0841D004CC693 /* example_ud.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DCB16F31EE0841D004CC693 /* example_ud.cpp */; }; 1DCB16F71EE0883F004CC693 /* libopennurbs_public.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DCB16F61EE0883F004CC693 /* libopennurbs_public.a */; }; - 1DCB16F91EE08847004CC693 /* libopennurbs_public_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DCB16F81EE08847004CC693 /* libopennurbs_public_freetype.a */; }; 1DCB16FB1EE08850004CC693 /* libopennurbs_public_zlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DCB16FA1EE08850004CC693 /* libopennurbs_public_zlib.a */; }; /* End PBXBuildFile section */ @@ -33,7 +32,6 @@ 1DCB16F01EE0835C004CC693 /* example_read.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = example_read.cpp; sourceTree = ""; }; 1DCB16F31EE0841D004CC693 /* example_ud.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = example_ud.cpp; path = ../example_userdata/example_ud.cpp; sourceTree = ""; }; 1DCB16F61EE0883F004CC693 /* libopennurbs_public.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public.a; path = "../../../../../../../../Library/Developer/Xcode/DerivedData/opennurbs_public-guyfbzppmwxsskejyvyxsmsqppib/Build/Products/Debug/libopennurbs_public.a"; sourceTree = ""; }; - 1DCB16F81EE08847004CC693 /* libopennurbs_public_freetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_freetype.a; path = ../freetype263/build/Debug/libopennurbs_public_freetype.a; sourceTree = ""; }; 1DCB16FA1EE08850004CC693 /* libopennurbs_public_zlib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_zlib.a; path = ../zlib/build/Debug/libopennurbs_public_zlib.a; sourceTree = ""; }; /* End PBXFileReference section */ @@ -44,7 +42,6 @@ files = ( 1D77E3891EE20FA700994B0B /* Cocoa.framework in Frameworks */, 1DCB16FB1EE08850004CC693 /* libopennurbs_public_zlib.a in Frameworks */, - 1DCB16F91EE08847004CC693 /* libopennurbs_public_freetype.a in Frameworks */, 1DCB16F71EE0883F004CC693 /* libopennurbs_public.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -78,7 +75,6 @@ children = ( 1D77E3881EE20FA700994B0B /* Cocoa.framework */, 1DCB16FA1EE08850004CC693 /* libopennurbs_public_zlib.a */, - 1DCB16F81EE08847004CC693 /* libopennurbs_public_freetype.a */, 1DCB16F61EE0883F004CC693 /* libopennurbs_public.a */, ); name = Frameworks; diff --git a/example_test/example_test.xcodeproj/project.pbxproj b/example_test/example_test.xcodeproj/project.pbxproj index ecc0f38e..d4dcc683 100644 --- a/example_test/example_test.xcodeproj/project.pbxproj +++ b/example_test/example_test.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 1D4E12A22016B8A400B90EA3 /* example_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D4E12A12016B8A400B90EA3 /* example_test.cpp */; }; 1D4E12A42016B99F00B90EA3 /* libopennurbs_public.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D4E12A52016B99F00B90EA3 /* libopennurbs_public.a */; }; 1D4E12A62016B9A600B90EA3 /* libopennurbs_public_zlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D4E12A72016B9A600B90EA3 /* libopennurbs_public_zlib.a */; }; - 1D4E12A82016B9AB00B90EA3 /* libopennurbs_public_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D4E12A92016B9AB00B90EA3 /* libopennurbs_public_freetype.a */; }; 1D4E12AB2016B9DD00B90EA3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D4E12AA2016B9DD00B90EA3 /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -31,7 +30,6 @@ 1D4E12A12016B8A400B90EA3 /* example_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = example_test.cpp; sourceTree = ""; }; 1D4E12A52016B99F00B90EA3 /* libopennurbs_public.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libopennurbs_public.a; sourceTree = BUILT_PRODUCTS_DIR; }; 1D4E12A72016B9A600B90EA3 /* libopennurbs_public_zlib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libopennurbs_public_zlib.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D4E12A92016B9AB00B90EA3 /* libopennurbs_public_freetype.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libopennurbs_public_freetype.a; sourceTree = BUILT_PRODUCTS_DIR; }; 1D4E12AA2016B9DD00B90EA3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -42,7 +40,6 @@ files = ( 1D4E12AB2016B9DD00B90EA3 /* Cocoa.framework in Frameworks */, 1D4E12A62016B9A600B90EA3 /* libopennurbs_public_zlib.a in Frameworks */, - 1D4E12A82016B9AB00B90EA3 /* libopennurbs_public_freetype.a in Frameworks */, 1D4E12A42016B99F00B90EA3 /* libopennurbs_public.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -71,7 +68,6 @@ isa = PBXGroup; children = ( 1D4E12AA2016B9DD00B90EA3 /* Cocoa.framework */, - 1D4E12A92016B9AB00B90EA3 /* libopennurbs_public_freetype.a */, 1D4E12A72016B9A600B90EA3 /* libopennurbs_public_zlib.a */, 1D4E12A52016B99F00B90EA3 /* libopennurbs_public.a */, ); diff --git a/example_userdata/example_userdata.xcodeproj/project.pbxproj b/example_userdata/example_userdata.xcodeproj/project.pbxproj index 0648828b..e5c06457 100644 --- a/example_userdata/example_userdata.xcodeproj/project.pbxproj +++ b/example_userdata/example_userdata.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 1D41D1B41EE0913600EB94A6 /* example_ud.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D41D1B11EE0913600EB94A6 /* example_ud.cpp */; }; 1D41D1B51EE0913600EB94A6 /* example_userdata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D41D1B31EE0913600EB94A6 /* example_userdata.cpp */; }; 1D41D1D41EE0A01200EB94A6 /* libopennurbs_public.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1D31EE0A01200EB94A6 /* libopennurbs_public.a */; }; - 1D41D1D61EE0A01700EB94A6 /* libopennurbs_public_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1D51EE0A01700EB94A6 /* libopennurbs_public_freetype.a */; }; 1D41D1D81EE0A01B00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1D71EE0A01B00EB94A6 /* libopennurbs_public_zlib.a */; }; 1D77E3911EE20FDC00994B0B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D77E3901EE20FDC00994B0B /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -33,7 +32,6 @@ 1D41D1B21EE0913600EB94A6 /* example_ud.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = example_ud.h; sourceTree = ""; }; 1D41D1B31EE0913600EB94A6 /* example_userdata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = example_userdata.cpp; sourceTree = ""; }; 1D41D1D31EE0A01200EB94A6 /* libopennurbs_public.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public.a; path = ../build/Debug/libopennurbs_public.a; sourceTree = ""; }; - 1D41D1D51EE0A01700EB94A6 /* libopennurbs_public_freetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_freetype.a; path = ../freetype263/build/Debug/libopennurbs_public_freetype.a; sourceTree = ""; }; 1D41D1D71EE0A01B00EB94A6 /* libopennurbs_public_zlib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_zlib.a; path = ../zlib/build/Debug/libopennurbs_public_zlib.a; sourceTree = ""; }; 1D77E3901EE20FDC00994B0B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -45,7 +43,6 @@ files = ( 1D77E3911EE20FDC00994B0B /* Cocoa.framework in Frameworks */, 1D41D1D81EE0A01B00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */, - 1D41D1D61EE0A01700EB94A6 /* libopennurbs_public_freetype.a in Frameworks */, 1D41D1D41EE0A01200EB94A6 /* libopennurbs_public.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -77,7 +74,6 @@ children = ( 1D77E3901EE20FDC00994B0B /* Cocoa.framework */, 1D41D1D71EE0A01B00EB94A6 /* libopennurbs_public_zlib.a */, - 1D41D1D51EE0A01700EB94A6 /* libopennurbs_public_freetype.a */, 1D41D1D31EE0A01200EB94A6 /* libopennurbs_public.a */, ); name = Frameworks; diff --git a/example_userdata/my_point_with_user_data.3dm b/example_userdata/my_point_with_user_data.3dm new file mode 100644 index 00000000..519a633f Binary files /dev/null and b/example_userdata/my_point_with_user_data.3dm differ diff --git a/example_write/example_write.cpp b/example_write/example_write.cpp index bbe147b7..d1fae99d 100644 --- a/example_write/example_write.cpp +++ b/example_write/example_write.cpp @@ -977,58 +977,58 @@ int main () filename = L"my_points.3dm"; rc = write_points_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_curves.3dm"; rc = write_curves_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_surfaces.3dm"; rc = write_surfaces_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_mesh.3dm"; rc = write_mesh_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_mesh_with_material.3dm"; rc = write_mesh_with_material_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_spot_light.3dm"; rc = write_spot_light_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_viewports.3dm"; rc = write_viewport_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); filename = L"my_trimmed_surface.3dm"; rc = write_trimmed_surface_example( filename, error_log ); if (rc) - message_log.Print("Successfully wrote %s.\n",filename); + message_log.Print(L"Successfully wrote %ls.\n",filename); else - message_log.Print("Errors while writing %s.\n",filename); + message_log.Print(L"Errors while writing %ls.\n",filename); ON::End(); diff --git a/example_write/example_write.xcodeproj/project.pbxproj b/example_write/example_write.xcodeproj/project.pbxproj index 2451f558..0d572d55 100644 --- a/example_write/example_write.xcodeproj/project.pbxproj +++ b/example_write/example_write.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 1D41D1B71EE0915B00EB94A6 /* example_write.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D41D1B61EE0915B00EB94A6 /* example_write.cpp */; }; 1D41D1BB1EE0919400EB94A6 /* example_ud.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D41D1B91EE0919400EB94A6 /* example_ud.cpp */; }; 1D41D1BF1EE09FC100EB94A6 /* libopennurbs_public.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1BE1EE09FC100EB94A6 /* libopennurbs_public.a */; }; - 1D41D1C11EE09FC800EB94A6 /* libopennurbs_public_freetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1C01EE09FC800EB94A6 /* libopennurbs_public_freetype.a */; }; 1D41D1C31EE09FCF00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D41D1C21EE09FCF00EB94A6 /* libopennurbs_public_zlib.a */; }; 1D77E38B1EE20FB300994B0B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D77E38A1EE20FB300994B0B /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -34,7 +33,6 @@ 1D41D1BA1EE0919400EB94A6 /* example_ud.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = example_ud.h; path = ../example_userdata/example_ud.h; sourceTree = ""; }; 1D41D1BC1EE091AE00EB94A6 /* opennurbs_public_examples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = opennurbs_public_examples.h; path = ../opennurbs_public_examples.h; sourceTree = ""; }; 1D41D1BE1EE09FC100EB94A6 /* libopennurbs_public.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public.a; path = ../build/Debug/libopennurbs_public.a; sourceTree = ""; }; - 1D41D1C01EE09FC800EB94A6 /* libopennurbs_public_freetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_freetype.a; path = ../freetype263/build/Debug/libopennurbs_public_freetype.a; sourceTree = ""; }; 1D41D1C21EE09FCF00EB94A6 /* libopennurbs_public_zlib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopennurbs_public_zlib.a; path = ../zlib/build/Debug/libopennurbs_public_zlib.a; sourceTree = ""; }; 1D77E38A1EE20FB300994B0B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -46,7 +44,6 @@ files = ( 1D77E38B1EE20FB300994B0B /* Cocoa.framework in Frameworks */, 1D41D1C31EE09FCF00EB94A6 /* libopennurbs_public_zlib.a in Frameworks */, - 1D41D1C11EE09FC800EB94A6 /* libopennurbs_public_freetype.a in Frameworks */, 1D41D1BF1EE09FC100EB94A6 /* libopennurbs_public.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -79,7 +76,6 @@ children = ( 1D77E38A1EE20FB300994B0B /* Cocoa.framework */, 1D41D1C21EE09FCF00EB94A6 /* libopennurbs_public_zlib.a */, - 1D41D1C01EE09FC800EB94A6 /* libopennurbs_public_freetype.a */, 1D41D1BE1EE09FC100EB94A6 /* libopennurbs_public.a */, ); name = Frameworks; diff --git a/example_write/my_curves.3dm b/example_write/my_curves.3dm new file mode 100644 index 00000000..8e4e1ff3 Binary files /dev/null and b/example_write/my_curves.3dm differ diff --git a/example_write/my_mesh.3dm b/example_write/my_mesh.3dm new file mode 100644 index 00000000..eadb3564 Binary files /dev/null and b/example_write/my_mesh.3dm differ diff --git a/example_write/my_mesh_with_material.3dm b/example_write/my_mesh_with_material.3dm new file mode 100644 index 00000000..b21f99df Binary files /dev/null and b/example_write/my_mesh_with_material.3dm differ diff --git a/example_write/my_points.3dm b/example_write/my_points.3dm new file mode 100644 index 00000000..60447df5 Binary files /dev/null and b/example_write/my_points.3dm differ diff --git a/example_write/my_spot_light.3dm b/example_write/my_spot_light.3dm new file mode 100644 index 00000000..a83a0c02 Binary files /dev/null and b/example_write/my_spot_light.3dm differ diff --git a/example_write/my_surfaces.3dm b/example_write/my_surfaces.3dm new file mode 100644 index 00000000..46cc97a6 Binary files /dev/null and b/example_write/my_surfaces.3dm differ diff --git a/example_write/my_trimmed_surface.3dm b/example_write/my_trimmed_surface.3dm new file mode 100644 index 00000000..917bcdbe Binary files /dev/null and b/example_write/my_trimmed_surface.3dm differ diff --git a/example_write/my_viewports.3dm b/example_write/my_viewports.3dm new file mode 100644 index 00000000..ec241109 Binary files /dev/null and b/example_write/my_viewports.3dm differ diff --git a/examples_linking_pragmas.h b/examples_linking_pragmas.h index 7a722844..67f59d3f 100644 --- a/examples_linking_pragmas.h +++ b/examples_linking_pragmas.h @@ -20,7 +20,9 @@ #pragma message( " --- statically linking opennurbs." ) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "opennurbs_public_staticlib.lib" "\"") #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "zlib.lib" "\"") +#if defined(OPENNURBS_FREETYPE_SUPPORT) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "freetype263.lib" "\"") +#endif #pragma comment(lib, "rpcrt4.lib") #pragma comment(lib, "shlwapi.lib") #endif diff --git a/makefile b/makefile index 9fcf211d..662828b8 100644 --- a/makefile +++ b/makefile @@ -789,25 +789,43 @@ example_userdata/example_ud.o : example_userdata/example_ud.h $(ON_INC) $(EXAMPLE_OBJ) : $(EXAMPLE_INC) $(ON_INC) -# opennurbs_freetype.h requires -I./freetype263/include -ON_OBJ_EXTRA_FLAGS = -DON_COMPILING_OPENNURBS -I./freetype263/include -$(ON_OBJ) : CFLAGS+=$(ON_OBJ_EXTRA_FLAGS) -$(ON_OBJ) : CCFLAGS+=$(ON_OBJ_EXTRA_FLAGS) -$(ON_OBJ) : $(ON_INC) - ZLIB_OBJ_EXTRA_FLAGS = -DMY_ZCALLOC -DZ_PREFIX $(ZLIB_OBJ) : CFLAGS+=$(ZLIB_OBJ_EXTRA_FLAGS) $(ZLIB_OBJ) : $(ZLIB_INC) -FREETYPE_OBJ_EXTRA_FLAGS = -DFT2_BUILD_LIBRARY -I./freetype263/include -$(FREETYPE_OBJ) : CFLAGS+=$(FREETYPE_OBJ_EXTRA_FLAGS) -$(FREETYPE_OBJ) : $(FREETYPE_INC) +######################################################## +## +## opennurbs without freetype +## +ON_OBJ_EXTRA_FLAGS = -DON_COMPILING_OPENNURBS +$(ON_OBJ) : CFLAGS+=$(ON_OBJ_EXTRA_FLAGS) +$(ON_OBJ) : CCFLAGS+=$(ON_OBJ_EXTRA_FLAGS) +$(ON_OBJ) : $(ON_INC) -$(OPENNURBS_LIB_FILE) : $(ON_OBJ) $(ZLIB_OBJ) $(FREETYPE_OBJ) +$(OPENNURBS_LIB_FILE) : $(ON_OBJ) $(ZLIB_OBJ) -$(RM) $@ - $(AR) $@ $(ON_OBJ) $(ZLIB_OBJ) $(FREETYPE_OBJ) + $(AR) $@ $(ON_OBJ) $(ZLIB_OBJ) $(RANLIB) $@ +######################################################## +## +## opennurbs with freetype +## +### opennurbs_freetype.h requires -I./freetype263/include +##ON_OBJ_EXTRA_FLAGS = -DON_COMPILING_OPENNURBS -I./freetype263/include +##$(ON_OBJ) : CFLAGS+=$(ON_OBJ_EXTRA_FLAGS) +##$(ON_OBJ) : CCFLAGS+=$(ON_OBJ_EXTRA_FLAGS) +##$(ON_OBJ) : $(ON_INC) +## +##FREETYPE_OBJ_EXTRA_FLAGS = -DFT2_BUILD_LIBRARY -I./freetype263/include +##$(FREETYPE_OBJ) : CFLAGS+=$(FREETYPE_OBJ_EXTRA_FLAGS) +##$(FREETYPE_OBJ) : $(FREETYPE_INC) +## +##$(OPENNURBS_LIB_FILE) : $(ON_OBJ) $(ZLIB_OBJ) $(FREETYPE_OBJ) +## -$(RM) $@ +## $(AR) $@ $(ON_OBJ) $(ZLIB_OBJ) $(FREETYPE_OBJ) +## $(RANLIB) $@ + example_read/example_read : example_read/example_read.o example_userdata/example_ud.o $(OPENNURBS_LIB_FILE) $(LINK) $(LINKFLAGS) example_read/example_read.o example_userdata/example_ud.o -L. -l$(OPENNURBS_LIB_NAME) -lm -o $@ @@ -830,5 +848,6 @@ clean : -$(RM) $(OPENNURBS_LIB_FILE) -$(RM) $(ON_OBJ) -$(RM) $(ZLIB_OBJ) + -$(RM) $(FREETYPE_OBJ) -$(RM) $(EXAMPLE_OBJ) -$(RM) $(EXAMPLES) diff --git a/opennurbs_annotationbase.cpp b/opennurbs_annotationbase.cpp index 2a296fd1..f132c982 100644 --- a/opennurbs_annotationbase.cpp +++ b/opennurbs_annotationbase.cpp @@ -3112,7 +3112,9 @@ bool ON_Annotation::SetAnnotationFont(const ON_Font* font, const ON_DimStyle* pa { if (nullptr != font) { - const ON_wString fontname = ON_Font::RichTextFontName(font, true); + SetFont(parent_style, *font); + + const ON_wString fontname = font->QuartetName(); //ON_Font::RichTextFontName(font, true); bool bold = font->IsBoldInQuartet(); bool italic = font->IsItalic(); diff --git a/opennurbs_apple_nsfont.cpp b/opennurbs_apple_nsfont.cpp index b1329898..090c93b4 100644 --- a/opennurbs_apple_nsfont.cpp +++ b/opennurbs_apple_nsfont.cpp @@ -21,12 +21,599 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif -#if defined(ON_RUNTIME_APPLE) && defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) #include "opennurbs_internal_glyph.h" #include "opennurbs_apple_nsfont.h" -void ON_AppleNSFontGetFontMetrics( +void ON_ManagedFonts::Internal_GetAppleInstalledCTFonts( + ON_SimpleArray& platform_font_list +) +{ + CFDictionaryRef options = nullptr; + CTFontCollectionRef availableFontCollection = CTFontCollectionCreateFromAvailableFonts(options); + if (nullptr == availableFontCollection ) + return; + CFArrayRef availableFontArray = CTFontCollectionCreateMatchingFontDescriptors(availableFontCollection); + if (nullptr == availableFontArray) + return; + const CFIndex count = CFArrayGetCount(availableFontArray); + for ( CFIndex idx = 0; idx < count; idx++) + { + CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(availableFontArray, idx); + if (nullptr == descriptor) + continue; + const CGAffineTransform *matrix = nullptr; + CGFloat size = 1000; + CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, size, matrix); + if (nullptr == font) + continue; + + const unsigned int units_per_em = CTFontGetUnitsPerEm(font); + if ( units_per_em > 0 && (CGFloat)units_per_em != size ) + { + CFRelease(font); + size = (CGFloat)units_per_em; + font = CTFontCreateWithFontDescriptor(descriptor, size, matrix); + if (nullptr == font) + continue; + } + + ON_Font* platform_font = new ON_Font(); + if (false == platform_font->SetFromAppleCTFont(font, false)) + { + CFRelease(font); + delete platform_font; + continue; + } + platform_font->SetPointSize(0); + platform_font_list.Append(platform_font); + } +} + +const ON_Font* ON_Font::GetManagedFontFromAppleCTFont( + CTFontRef apple_font, + bool bAnnotationFont +) +{ + ON_Font font_characteristics; + if ( false == font_characteristics.SetFromAppleCTFont(apple_font,bAnnotationFont) ) + return nullptr; + return font_characteristics.ManagedFont(); +} + +unsigned int ON_Font::AppleCTFontUnitsPerEm(CTFontRef apple_font) +{ + const unsigned int units_per_em + = (nullptr != apple_font) + ? CTFontGetUnitsPerEm(apple_font) + : 0; + return units_per_em; +} + + +CTFontRef ON_Font::AppleCTFontSetSize( + CTFontRef apple_font, + CGFloat size, + bool bReleaseOriginal +) +{ + for(;;) + { + if (nullptr == apple_font) + break; + const unsigned int units_per_em = ON_Font::AppleCTFontUnitsPerEm(apple_font); + if ( units_per_em <= 0 ) + break; + const CGFloat units_per_em_size = (CGFloat)units_per_em; + if ( !(units_per_em_size > 0.0) ) + break; + const CGFloat size0 = CTFontGetSize(apple_font); + if ( fabs(size0 - units_per_em_size) <= 0.0001 ) + break; + + const CGAffineTransform *matrix = nullptr; + CTFontDescriptorRef attributes = nullptr; + CTFontRef apple_font1 = CTFontCreateCopyWithAttributes(apple_font, units_per_em_size, matrix, attributes); + if ( nullptr == apple_font1) + break; + + const unsigned int units_per_em1 = CTFontGetUnitsPerEm(apple_font1); + if ( units_per_em1 != units_per_em) + { + CFRelease(apple_font1); + break; + } + + if (bReleaseOriginal) + CFRelease(apple_font); + return apple_font1; + } + return apple_font; +} + +ON_PANOSE1 ON_Font::AppleCTFontPANOSE1(CTFontRef apple_font) +{ + ON_PANOSE1 panose1 = ON_PANOSE1::Zero; + + CFDataRef os2Table = nullptr; + for(;;) + { + if (nullptr == apple_font) + break; + + // The OS/2 table contains the PANOSE1 classification + os2Table = CTFontCopyTable(apple_font, kCTFontTableOS2, kCTFontTableOptionNoOptions); + + if (nullptr == os2Table) + break; + + // The PANOSE data is in bytes 32-42 of the table according to the TrueType and OpenType specs. + if (CFDataGetLength(os2Table) < 42) + { + // Truncated table? + break; + } + + uint8_t panose[10] = {}; + CFDataGetBytes(os2Table, (CFRange){ 32, 10 }, panose); + if (panose[0] > 5) + break; // invalid PANOSE1 family kind + + panose1.SetTenBytes(panose); + break; + } + if (nullptr != os2Table) + CFRelease(os2Table); + + return panose1; +} + +bool ON_Font::SetFromAppleCTFont(CTFontRef apple_font, bool bAnnotationFont) +{ + if (nullptr == apple_font) + return false; + + if ( false == this->ModificationPermitted(OPENNURBS__FUNCTION__,__FILE__,__LINE__) ) + return false; + + *this = ON_Font::Unset; + + const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(apple_font); + const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font); + const ON_wString face_name = ON_Font::AppleCTFontFaceName(apple_font); + + // Set Windows LOGFONT.lfFaceName to something not empty there is some hope this + // font might work in Rhino 5 for Windows too. + // https://mcneel.myjetbrains.com/youtrack/issue/RH-37074 + const ON_wString windows_logfont_name = family_name; + + const bool rc = postscript_name.IsNotEmpty() || family_name.IsNotEmpty(); + if (rc) + { + m_loc_postscript_name = postscript_name; + m_en_postscript_name = postscript_name; + m_loc_family_name = family_name; + m_en_family_name = family_name; + m_loc_face_name = face_name; + m_en_face_name = face_name; + m_loc_windows_logfont_name = windows_logfont_name; + m_en_windows_logfont_name = windows_logfont_name; + + // Can get font metrics from NSFontDescriptor + // https://developer.apple.com/library/content/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html + // defaultLineHeight(for theFont: NSFont) + // fd.xHeight, fd.ascender, fd.descender, fd.capHeight, fd.defaultLineHeightForFont + + // Set weight - used if this font needs sustution on another computer + // https://mcneel.myjetbrains.com/youtrack/issue/RH-37075 + + double apple_font_weight_trait = ON_UNSET_VALUE; + double apple_font_width_trait = ON_UNSET_VALUE; + double apple_font_slant_trait = ON_UNSET_VALUE; + ON_Font::Weight weight = ON_Font::Weight::Normal; + ON_Font::Style style = ON_Font::Style::Upright; + ON_Font::Stretch stretch = ON_Font::Stretch::Medium; + + CTFontDescriptorRef descriptor = CTFontCopyFontDescriptor(apple_font); + if (nullptr != descriptor) + { + CFDictionaryRef dict = (CFDictionaryRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); + if (nullptr != dict) + { + const CFNumberRef appleSymbolicTrait = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontSymbolicTrait); + const CFNumberRef appleWeight = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontWeightTrait); + const CFNumberRef appleWidth = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontWidthTrait); + const CFNumberRef appleSlant = (CFNumberRef)CFDictionaryGetValue(dict, kCTFontSlantTrait); + + if (nullptr != appleSymbolicTrait) + { + int i=0; + if ( CFNumberGetValue(appleSymbolicTrait, kCFNumberIntType, &i) ) + { + // Use the kCTFontSymbolicTrait key to access the symbolic traits value from the font traits dictionary. + // The value is returned as a CFNumberRef. + if (0 != (kCTFontTraitItalic & i)) + style = ON_Font::Style::Italic; + if (0 != (kCTFontTraitBold & i)) + weight = ON_Font::Weight::Bold; + if (0 != (kCTFontTraitCondensed & i)) + stretch = ON_Font::Stretch::Condensed; + else if (0 != (kCTFontTraitExpanded & i)) + stretch = ON_Font::Stretch::Expanded; + } + } + + if (nullptr != appleWeight) + { + double x = apple_font_weight_trait; + if ( CFNumberGetValue(appleWeight, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0) + { + // Use the kCTFontWeightTrait key to access the normalized weight trait from the font traits dictionary. + // The value returned is a CFNumberRef representing a float value between -1.0 and 1.0 for normalized weight. + // The value of 0.0 corresponds to the regular or medium font weight. + apple_font_weight_trait = x; + ON_Font::WeightFromAppleFontWeightTrait(apple_font_weight_trait); + } + } + + if (nullptr != appleWidth) + { + double x = apple_font_weight_trait; + if ( CFNumberGetValue(appleWidth, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0 ) + { + // Use the kCTFontWidthTrait key to access the normalized proportion trait from the font traits dictionary. + // This value corresponds to the relative inter-glyph spacing for a given font. + // The value returned is a CFNumberRef representing a float between -1.0 and 1.0. + // The value of 0.0 corresponds to regular glyph spacing while negative values represent condensed glyph spacing. + apple_font_width_trait = x; + if (-1.0 == apple_font_width_trait) + stretch = ON_Font::Stretch::Condensed; + else if (1.0 == apple_font_width_trait) + stretch = ON_Font::Stretch::Expanded; + } + } + + if (nullptr != appleSlant) + { + double x = apple_font_weight_trait; + if ( CFNumberGetValue(appleSlant, kCFNumberFloat64Type, &x) && -1.0 <= x && x <= 1.0 ) + { + // Use this key to access the normalized slant angle from the font traits dictionary. + // The value returned is a CFNumberRef representing a float value between -1.0 and 1.0 for normalized slant angle. + // The value or 0.0 corresponds to 0 degree clockwise rotation from the vertical and 1.0 corresponds to 30 degrees clockwise rotation. + apple_font_slant_trait = x; + if (1.0 == apple_font_slant_trait) + style = ON_Font::Style::Italic; + } + } + CFRelease(dict); + } + CFRelease(descriptor); + } + + if (-1.0 <= apple_font_weight_trait && apple_font_weight_trait <= 1.0) + SetAppleFontWeightTrait(apple_font_weight_trait); + else + SetFontWeight(weight); + + m_font_style = style; + m_font_stretch = stretch; + + // Saving point size added January 2018. + const double point_size = (double)CTFontGetSize(apple_font); + m_point_size + = (false == bAnnotationFont && ON_Font::IsValidPointSize(point_size) && point_size < ((double)ON_Font::AnnotationFontApplePointSize)) + ? point_size + : 0.0; // indicates annotation size (units per em) will be used + + m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset; + + // do this again because some of the above calls can modify description + m_loc_postscript_name = postscript_name; + m_en_postscript_name = m_loc_postscript_name; + m_loc_family_name = family_name; + m_en_family_name = m_loc_family_name; + m_loc_face_name = face_name; + m_en_face_name = m_loc_face_name; + m_loc_windows_logfont_name = windows_logfont_name; + m_en_windows_logfont_name = m_loc_windows_logfont_name; + m_panose1 = ON_Font::AppleCTFontPANOSE1(apple_font); + + SetFontOrigin(ON_Font::Origin::AppleFont); + } + + return rc; +} + +const ON_wString ON_Font::AppleCTFontPostScriptName( + CTFontRef apple_font +) +{ + if (nullptr == apple_font) + return ON_wString::EmptyString; + + CFStringRef applePostScriptName = CTFontCopyPostScriptName(apple_font); + ON_wString postscript_name(applePostScriptName); + if (nullptr != applePostScriptName) + CFRelease(applePostScriptName); + postscript_name.TrimLeftAndRight(); + return postscript_name; +} + + +const ON_wString ON_Font::AppleCTFontFamilyName( + CTFontRef apple_font +) +{ + CFStringRef appleFamilyName = CTFontCopyFamilyName(apple_font); + ON_wString family_name(appleFamilyName); + if (nullptr != appleFamilyName) + CFRelease(appleFamilyName); + family_name.TrimLeftAndRight(); + return family_name; +} + + +const ON_wString ON_Font::AppleCTFontDisplayName( + CTFontRef apple_font +) +{ + CFStringRef appleDisplayName = CTFontCopyDisplayName(apple_font); + ON_wString display_name(appleDisplayName); + if (nullptr != appleDisplayName) + CFRelease(appleDisplayName); + display_name.TrimLeftAndRight(); + return display_name; +} + +static const ON_wString Internal_FaceNameFromAppleDisplayAndFamilyName( + const ON_wString& appleFontDisplayName, + const ON_wString& appleFontFamilyName +) +{ + if ( appleFontDisplayName.IsEmpty() || appleFontFamilyName.IsEmpty()) + return ON_wString::EmptyString; + + const wchar_t* family_and_face_str = static_cast(appleFontDisplayName); + const wchar_t* family_str = static_cast(appleFontFamilyName); + if (nullptr == family_and_face_str || nullptr == family_str) + return ON_wString::EmptyString; + + // It is not always the case that the beginning of + // appleFontDisplayName exactly matches + // appleFontFamilyName. + // We need to ignore spaces and hyphens. + for (;;) + { + wchar_t a = *family_str; + while (ON_wString::Space == a || ON_wString::HyphenMinus == a) + a = *(++family_str); + + wchar_t b = *family_and_face_str; + while (ON_wString::Space == b || ON_wString::HyphenMinus == b) + b = *(++family_and_face_str); + + if (0 == a || 0 == b) + break; + a = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,a); + b = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,b); + if (a != b) + break; + family_str++; + family_and_face_str++; + } + for(wchar_t c = *family_and_face_str; ON_wString::Space == c || ON_wString::HyphenMinus == c; c = *family_and_face_str ) + ++family_and_face_str; + ON_wString face_name(family_and_face_str); + face_name.TrimRight(); + if ( face_name.IsEmpty()) + { + // This is what Apple MacOS uses in FontBook and other font selection UI. + // https://developer.apple.com/documentation/appkit/nsfontmanager/1462316-availablemembersoffontfamily?language=objc + // A more complicated approach that yields the same results is: + // https://developer.apple.com/documentation/coretext/1511240-ctfontcopyname?language=objc + // using kCTFontSubFamilyNameKey This retrieves the TrueType "subfamily" font name + // TrueType names [1]=family [2]=subfamily (which we call "face"), [6] = postscript. + // Reference: Section 6 of https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=IWS-Chapter08 + // + // None of the approaches above is localized, so a hard coded English word works as + // well as using the Apple SDK. + face_name = L"Regular"; + } + return face_name; +} + +const ON_wString ON_Font::AppleCTFontFaceName( + CTFontRef apple_font +) +{ + for(;;) + { + if (nullptr == apple_font) + break; + + for(;;) + { + CFStringRef appleStyleName = CTFontCopyName(apple_font,kCTFontStyleNameKey); + if (nullptr == appleStyleName) + break; + ON_wString style_name(appleStyleName); + CFRelease(appleStyleName); + style_name.TrimRight(); + if ( style_name.IsEmpty() ) + break; + return style_name; + } + + // Fallback - In Sep 12, 2018 tests of 603 faces, the no longer needed with the swtich to CTFont + // Leaving this here to handle unknown buggy fonts. + const ON_wString family_and_face_name = ON_Font::AppleCTFontDisplayName(apple_font); + const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font); + const ON_wString hack_style_name = Internal_FaceNameFromAppleDisplayAndFamilyName(family_and_face_name,family_name); + return hack_style_name; + } + + return ON_wString::EmptyString; +} + +CTFontRef ON_Font::AppleCTFont() const +{ + // Using PointSize() added January 2018. + const double pointSize + = ON_Font::IsValidPointSize(m_point_size) + ? m_point_size + : 0.0; + return ON_Font::AppleCTFont(pointSize); +} + + CTFontRef ON_Font::AppleCTFont( + const wchar_t* name, + double pointSize +) +{ + ON_wString local_name(name); + local_name.TrimLeftAndRight(); + if (local_name.IsEmpty()) + return nullptr; + + const bool bHavePointSize = ( pointSize > 0.0 ); + const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 ); + const CGAffineTransform *matrix = nullptr; + + CFStringRef appleName = local_name.ToAppleCFString(); + if (nullptr == appleName) + return nullptr; + CTFontRef appleFont = CTFontCreateWithName(appleName, size, matrix); + CFRelease(appleName); + if (nullptr == appleFont) + return nullptr; + if (false == bHavePointSize) + appleFont = ON_Font::AppleCTFontSetSize(appleFont, 0.0, true); + + // DEBUGGING TEST + if (nullptr != appleFont) + { + ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(appleFont); + if ( false == ON_wString::EqualOrdinal(postscript_name,local_name,true) ) + return appleFont; + } + + return appleFont; +} + + +CTFontRef ON_Font::AppleCTFont(double pointSize) const +{ + const bool bHavePointSize = ( pointSize > 0.0 ); + const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 ); + + CTFontRef appleCTFont = nullptr; + const ON_Font* installed_font = nullptr; + ON_wString tested_names[7]; + for(int name_dex = 0; name_dex < 7; name_dex++) + { + ON_wString name; + if ( 2 == name_dex) + { + installed_font = InstalledFont(true); + if (nullptr == installed_font) + installed_font = &ON_Font::Default; + } + + switch (name_dex) + { + case 0: + name = m_loc_postscript_name; + break; + case 1: + name = m_en_postscript_name; + break; + case 2: + name = installed_font->m_loc_postscript_name; + break; + case 3: + name = installed_font->m_en_postscript_name; + break; + case 4: + name = installed_font->m_loc_family_name; + break; + case 5: + name = installed_font->m_en_family_name; + break; + case 6: + name = installed_font->Description(); + break; + default: + break; + } + + if ( name.IsNotEmpty() ) + { + for ( int i = 0; i < name_dex; i++ ) + { + if (tested_names[name_dex].IsEmpty()) + continue; + if ( ON_wString::EqualOrdinal(name,tested_names[name_dex],true) ) + { + name = ON_wString::EmptyString; + break; + } + } + } + if ( name.IsEmpty() ) + continue; + tested_names[name_dex] = name; + + CTFontRef appleCTFontWithName = ON_Font::AppleCTFont(name,size); + if (nullptr == appleCTFontWithName) + continue; + const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(appleCTFontWithName); + if ( ON_wString::EqualOrdinal(postscript_name,name,true)) + { + if (nullptr != appleCTFont) + CFRelease(appleCTFont); + appleCTFont = appleCTFontWithName; + break; + } + if ( nullptr == appleCTFont ) + appleCTFont = appleCTFontWithName; + else + CFRelease(appleCTFontWithName); + } + + if ( nullptr != appleCTFont && false == bHavePointSize) + { + // make sure size = font_units_per_em + appleCTFont = ON_Font::AppleCTFontSetSize(appleCTFont, 0.0, true); + } + + return appleCTFont; +} + +static unsigned int Internal_AppleUnitsPerEm( CTFontRef appleFont, double* pointSizeToUPM ) +{ + unsigned int font_design_units_per_M = CTFontGetUnitsPerEm(appleFont); + if ( font_design_units_per_M <= 0 ) + { + ON_ERROR("CTFontGetUnitsPerEm(appleFont) returned zero."); + font_design_units_per_M = 1000; + } + + if (nullptr != pointSizeToUPM) + { + const double point_size = (double)CTFontGetSize(appleFont); + const double UPM = font_design_units_per_M; + + // This scaling approach recovers correct values in font design units + // when compared with values from DWrite and FreeType. + *pointSizeToUPM = (point_size > 0.0 && fabs(point_size-UPM) > 0.001 ) ? (UPM/point_size) : 1.0; + } + + return font_design_units_per_M; +} + +void ON_AppleFontGetFontMetrics( const ON_Font* font, ON_FontMetrics& font_metrics ) @@ -40,39 +627,33 @@ void ON_AppleNSFontGetFontMetrics( if (nullptr == font) break; - NSFont* appleFont = font->AppleFont(); + CTFontRef appleFont = font->AppleCTFont(); if (nullptr == appleFont) break; - // No way to get actual font design units per M from NSFont API? - // 2048 is a common value and the default. - unsigned int font_design_units_per_M = 2048; -#if defined(OPENNURBS_FREETYPE_SUPPORT) - const unsigned int freetypeUPM = ON_FreeTypeGetFontUnitsPerM(font); - if (freetypeUPM > 0) - font_design_units_per_M = freetypeUPM; -#endif + double pointSizeToUPM = 1.0; + const unsigned int font_design_units_per_M = Internal_AppleUnitsPerEm(appleFont,&pointSizeToUPM); - const double point_size = appleFont.pointSize; const double UPM = font_design_units_per_M; - // This scaling approach recovers correct values in font design units - // when compared with values from DWrite and FreeType. - const double pointSizeToUPM = (point_size > 0.0) ? (UPM/point_size) : 1.0; - - const double leading = pointSizeToUPM*((double)appleFont.leading); - const double ascent = pointSizeToUPM*((double)appleFont.ascender); - const double descent = pointSizeToUPM*((double)appleFont.descender); + const double leading = pointSizeToUPM*((double)CTFontGetLeading(appleFont)); + + const double ascent = pointSizeToUPM * ((double)CTFontGetAscent(appleFont)); + + const CGFloat ct_descent_sign = -1; + const double descent = pointSizeToUPM * ((double)(ct_descent_sign*CTFontGetDescent(appleFont))); const double line_space1 = ascent - descent + leading; - const double line_space2 = pointSizeToUPM*((double)(appleFont.ascender-appleFont.descender+appleFont.leading)); + const double line_space2 + = (1.0 != pointSizeToUPM) + ? (pointSizeToUPM*((double)(CTFontGetAscent(appleFont)-(ct_descent_sign*CTFontGetDescent(appleFont))+CTFontGetLeading(appleFont)))) + : line_space1; const double line_space = (line_space2>=line_space1) ? line_space2 : line_space1; - const double ascent_of_x = pointSizeToUPM*((double)appleFont.xHeight); - double ascent_of_capital = pointSizeToUPM*((double)appleFont.capHeight); - - const double underscore_position = pointSizeToUPM*((double)appleFont.underlinePosition); - const double underscore_thickness = pointSizeToUPM*((double)appleFont.underlineThickness); + const double ascent_of_x = pointSizeToUPM*((double)CTFontGetXHeight(appleFont)); + const double ascent_of_capital = pointSizeToUPM*((double)CTFontGetCapHeight(appleFont)); + const double underscore_position = pointSizeToUPM*((double)CTFontGetUnderlinePosition(appleFont)); + const double underscore_thickness = pointSizeToUPM*((double)CTFontGetUnderlineThickness(appleFont)); font_metrics = ON_FontMetrics::Unset; font_metrics.SetHeights( @@ -109,36 +690,291 @@ void ON_AppleNSFontGetFontMetrics( return; } -#if defined(ON_NSFONT_GLYPH_SUPPORT_WIP) +static int Internal_IntFloor(double x) +{ + double f = floor(x); + double c = ceil(x); + if ( (1.0 == (c-f)) && ((c-x) <= 1.0e-4 )) + return (int)c; + return (int)f; +} -bool ON_AppleNSFontGetGlyphMetrics( - NSFont* appleFont, - unsigned int font_design_units_per_M, +static int Internal_IntCeil(double x) +{ + double f = floor(x); + double c = ceil(x); + if ( (1.0 == (c-f)) && ((x-f) <= 1.0e-4 )) + return (int)f; + return (int)c; +} + +bool ON_AppleFontGetGlyphMetrics( + CTFontRef appleFont, unsigned int glyphIndex, class ON_TextBox& glyph_metrics ) { - // TODO - make this work on MacOS glyph_metrics = ON_TextBox::Unset; + + for(;;) + { + if (0 == glyphIndex) + break; + if (nullptr == appleFont) + break; + + const CGGlyph glyphs[2] = {(CGGlyph)glyphIndex,0}; + if ( ((unsigned int)glyphs[0]) != glyphIndex ) + break; // CGGlyph is a short and glyphIndex is too large. + const CFIndex count = 1; + + CGRect boundingRects[2]; + CGRect glyphRect = CTFontGetBoundingRectsForGlyphs(appleFont, kCTFontOrientationHorizontal, glyphs, boundingRects, count); + if ( boundingRects[0].size.height != 0.0f || boundingRects[0].size.width != 0.0f ) + glyphRect = boundingRects[0]; + + CGSize advances[2]; + double glyphAdvanceX = CTFontGetAdvancesForGlyphs(appleFont, kCTFontOrientationHorizontal, glyphs, advances, count); + if (advances[0].width != 0.0f) + glyphAdvanceX = advances[0].width; + + double glyphAdvanceY = CTFontGetAdvancesForGlyphs(appleFont, kCTFontOrientationVertical, glyphs, advances, count); + if (advances[0].width != 0.0f) + glyphAdvanceY = advances[0].width; // width is correct - the vertical advance is returned in width. + + //CGSize translations[2]; + //CTFontGetVerticalTranslationsForGlyphs(appleFont, glyphs, translations, count); + + double pointSizeToUPM = 1.0; + const unsigned int font_design_units_per_M = Internal_AppleUnitsPerEm(appleFont,&pointSizeToUPM); + if (0 == font_design_units_per_M) + pointSizeToUPM = 1.0; + + glyph_metrics.m_bbmin.i = Internal_IntFloor(pointSizeToUPM*glyphRect.origin.x); + glyph_metrics.m_bbmin.j = Internal_IntFloor(pointSizeToUPM*glyphRect.origin.y); + + glyph_metrics.m_bbmax.i = Internal_IntCeil(pointSizeToUPM*(glyphRect.origin.x + glyphRect.size.width)); + glyph_metrics.m_bbmax.j = Internal_IntCeil(pointSizeToUPM*(glyphRect.origin.y + glyphRect.size.height)); + + glyph_metrics.m_advance.i = Internal_IntCeil(pointSizeToUPM*glyphAdvanceX); + glyph_metrics.m_advance.j = Internal_IntCeil(pointSizeToUPM*glyphAdvanceY); + + return true; + } return false; } -bool ON_AppleNSFontGetGlyphOutline( - NSFont* appleFont, - unsigned int font_design_units_per_M, +class ON_AppleGlyphOutlineAccumlator : public ON_OutlineAccumulator +{ +public: + ON_AppleGlyphOutlineAccumlator() = default; + ~ON_AppleGlyphOutlineAccumlator() = default; + + static void Internal_PathApplierFunction( + void *info, + const CGPathElement *element + ); + +private: + static const ON_2fPoint Internal_Point(CGPoint p); + +private: + ON_AppleGlyphOutlineAccumlator(const ON_AppleGlyphOutlineAccumlator&) = delete; + ON_AppleGlyphOutlineAccumlator& operator=(const ON_AppleGlyphOutlineAccumlator&) = delete; +}; + +const ON_2fPoint ON_AppleGlyphOutlineAccumlator::Internal_Point(CGPoint p) +{ + return ON_2fPoint((float)p.x, (float)p.y); +} + +void ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction( + void *info, + const CGPathElement *element +) +{ + if (nullptr == info || nullptr == element) + return; + + ON_AppleGlyphOutlineAccumlator* acc = (ON_AppleGlyphOutlineAccumlator*)info; + + switch (element->type) + { + case kCGPathElementMoveToPoint: + if (nullptr != element->points) + { + acc->BeginFigure( + ON_OutlineFigurePoint::Type::BeginFigureUnknown, + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]) + ); + } + break; + + case kCGPathElementAddLineToPoint: + if (nullptr != element->points) + { + acc->AppendLine( + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]) + ); + } + break; + + case kCGPathElementAddQuadCurveToPoint: + if (nullptr != element->points) + { + acc->AppendQuadraticBezier( + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]), + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[1]) + ); + } + break; + + case kCGPathElementAddCurveToPoint: // cubic bezier + if (nullptr != element->points) + { + acc->AppendCubicBezier( + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[0]), + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[1]), + ON_AppleGlyphOutlineAccumlator::Internal_Point(element->points[2]) + ); + } + break; + + case kCGPathElementCloseSubpath: + acc->EndFigure( + ON_OutlineFigurePoint::Type::EndFigureClosed + ); + break; + + default: + break; + }; + +} + +bool ON_AppleFontGetGlyphOutline( + CTFontRef appleFont, unsigned int glyphIndex, ON_OutlineFigure::Type figure_type, class ON_Outline& outline ) { - // TODO - make this work on MacOS outline = ON_Outline::Unset; - return false; + + if (nullptr == appleFont) + return false; + + if ( glyphIndex <= 0 ) + return false; + + const CGGlyph appleGlyphIndex = (CGGlyph)glyphIndex; + if (((unsigned int)appleGlyphIndex) != glyphIndex) + { + // glyphIndex too large - currently CGGlyph is an unsigned short. + return false; + } + + CGPathRef applePath = CTFontCreatePathForGlyph(appleFont, appleGlyphIndex, nullptr); + if (nullptr == applePath) + return false; + + unsigned int font_design_units_per_M = CTFontGetUnitsPerEm(appleFont); + const CGFloat point_size = CTFontGetSize(appleFont); + if ( false == (point_size== (CGFloat)font_design_units_per_M)) + { + ON_WARNING("CTFontGetSize() != CTFontGetUnitsPerEm()"); + font_design_units_per_M =(unsigned int)ceil(point_size); + } + + ON_AppleGlyphOutlineAccumlator acc; + acc.BeginGlyphOutline(font_design_units_per_M, figure_type, &outline); + CGPathApply(applePath, &acc, ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction); + CGPathRelease(applePath); + acc.EndOutline(); + + // Add Glyph metrics to outline + ON_TextBox glyph_metrics_in_font_design_units; + bool bHaveGlyphMetrics = ON_AppleFontGetGlyphMetrics(appleFont, appleGlyphIndex, glyph_metrics_in_font_design_units); + if ( bHaveGlyphMetrics ) + outline.SetGlyphMetrics(glyph_metrics_in_font_design_units); + else + outline.SetGlyphMetrics(ON_TextBox::Unset); + + /* + for(;;) + { + if ( 0 == outline.FigureCount()) + break; + ON_wString postscriptName(CTFontCopyPostScriptName(appleFont)); + postscriptName.Remove(ON_String::Space); + postscriptName.Remove(ON_wString::HyphenMinus); + if ( false == postscriptName.EqualOrdinal(L"AppleColorEmoji",true) ) + break; + // AppleColorEmoji contains bitmaps and no outlines. + // Use NotoEmoji as a fallback. + CTFontRef notoEmoji = CTFontCreateWithName(CFSTR("NotoEmoji"), font_design_units_per_M, nullptr); + if (nullptr == notoEmoji) + break; + + CGPathRef applePath = CTFontCreatePathForGlyph(notoEmoji, appleGlyphIndex, nullptr); + if (nullptr == applePath) + return false; + + + outline = ON_Outline::Unset; + ON_AppleGlyphOutlineAccumlator acc2; + acc2.BeginGlyphOutline(font_design_units_per_M, figure_type, &outline); + CGPathApply(applePath, &acc2, ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction); + CGPathRelease(applePath); + acc2.EndOutline(); + CFRelease(notoEmoji); + } + */ + + return true; } -ON__UINT_PTR ON_AppleNSFontGetGlyphMetrics( +unsigned int ON_AppleFontGlyphIndex( + CTFontRef appleFont, + unsigned int unicode_code_point +) +{ + unsigned int glyphIndex = 0; + + for (;;) + { + if (false == ON_IsValidUnicodeCodePoint(unicode_code_point)) + break; + + ON__UINT16 utf16[3] = {}; + const int utf16_count = ON_EncodeUTF16(unicode_code_point, utf16); + if (utf16_count < 1 || utf16_count > 2) + break; + + // UniChar is used for Apple UTF-16 endcodings + const UniChar apple_utf16[3] = { (UniChar)utf16[0], (UniChar)utf16[1], (UniChar)0 }; + + // glyphs[] includes an element for the NULL terminator and an extra element + // in case CTFontGetGlyphsForCharacters is buggy when handling surrogate pairs. + CGGlyph glyphs[3] = {}; + if (false == CTFontGetGlyphsForCharacters(appleFont, apple_utf16, glyphs, (NSInteger)utf16_count)) + break; + if (0 == glyphs[0]) + break; + if (0 != glyphs[1] || 0 != glyphs[2]) + break; + + glyphIndex = (unsigned int)glyphs[0]; + + break; + } + + return glyphIndex; +} + + +unsigned int ON_AppleFontGetGlyphMetrics( const ON_FontGlyph* glyph, - unsigned int font_design_units_per_M, ON_TextBox& glyph_metrics ) { @@ -154,33 +990,21 @@ ON__UINT_PTR ON_AppleNSFontGetGlyphMetrics( if (nullptr == font) return 0; - NSFont* appleFont = font->AppleFont(); + CTFontRef appleFont = font->AppleCTFont(); if (nullptr == appleFont) return 0; - ON__UINT_PTR glpyh_id = 0; + const unsigned int glyph_index = ON_AppleFontGlyphIndex(appleFont, glyph->CodePoint()); + if (0 == glyph_index) + return 0; + + ON_AppleFontGetGlyphMetrics(appleFont, glyph_index, glyph_metrics); - bool rc = false; - - // NEVER TESTED - //NSString* baseString = [NSString stringWithFormat:@"%C", glpyh->CodePoint()]; - //NSGlyphInfo* glyphInfo = [NSGlyphInfo glyphInfoWithGlyph:glyph forFont:appleFont baseString: baseString]; - //if (nullptr == glyphInfo) - // return 0; - - //glyph_id = (ON__UINT_PTR)glyphInfo.glyphID(); - - //const bool rc - // = (0 != glpyh_id) - // ? ON_AppleNSFontGetGlyphMetrics(appleFont, (unsigned int)glpyh_id, glyph_metrics) - // : false; - - return rc ? glpyh_id : 0; + return glyph_index; } -bool ON_AppleNSFontGetGlyphOutline( +bool ON_AppleFontGetGlyphOutline( const ON_FontGlyph* glyph, - unsigned int font_design_units_per_M, ON_OutlineFigure::Type figure_type, class ON_Outline& outline ) @@ -193,12 +1017,10 @@ bool ON_AppleNSFontGetGlyphOutline( if (false == glyph->CodePointIsSet()) return false; - ON__UINT64 glpyh_id = (ON__UINT64)glyph->FontGlyphId(); - if (glpyh_id > 0xFFFFFFFF) + const unsigned int glyphIndex = glyph->FontGlyphIndex(); + if (0==glyphIndex) return false; - const unsigned int glyphIndex = (unsigned int)glpyh_id; - const ON_Font* font = glyph->Font(); if (nullptr == font) return false; @@ -212,13 +1034,12 @@ bool ON_AppleNSFontGetGlyphOutline( } } - NSFont* appleFont = font->AppleFont(); + CTFontRef appleFont = font->AppleCTFont(); if (nullptr == appleFont) return false; - const bool rc = ON_AppleNSFontGetGlyphOutline( + const bool rc = ON_AppleFontGetGlyphOutline( appleFont, - font_design_units_per_M, glyphIndex, figure_type, outline @@ -227,5 +1048,451 @@ bool ON_AppleNSFontGetGlyphOutline( return rc; } +void ON_Font::DumpCTFont( + CTFontRef apple_font, + ON_TextLog& text_log +) +{ + if (nullptr == apple_font) + { + text_log.Print("CTFont = nullptr\n"); + return; + } + + text_log.Print("CTFont\n"); + text_log.PushIndent(); + + const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(apple_font); + text_log.Print(L"CTFont PostScriptName: \"%ls\"\n",static_cast(postscript_name)); + + const ON_wString display_name = ON_Font::AppleCTFontDisplayName(apple_font); + text_log.Print(L"CTFont DisplayName: \"%ls\"\n",static_cast(display_name)); + + const ON_wString family_name = ON_Font::AppleCTFontFamilyName(apple_font); + text_log.Print(L"CTFont FamilyName: \"%ls\"\n",static_cast(family_name)); + + // Apple NSFont and MacOS do not have "face names" as a font attribute. + // This is the "typeface" name shown in Apple FontBook + const ON_wString fake_face_name = ON_Font::AppleCTFontFaceName(apple_font); + text_log.Print(L"CTFont FontBook typeface: \"%ls\"\n",static_cast(fake_face_name)); + + const unsigned int UPM = ON_Font::AppleCTFontUnitsPerEm(apple_font); + text_log.Print("CTFont UnitsPerEm: %u\n",UPM); + + const double point_size = (double)CTFontGetSize(apple_font); + text_log.Print("CTFont Size: %g points\n",point_size); + +#if 0 + text_log.Print(L"CTFont Names:\n"); + text_log.PushIndent(); + + CFStringRef keys[] = + { + kCTFontCopyrightNameKey, + kCTFontFamilyNameKey, + kCTFontSubFamilyNameKey, + kCTFontStyleNameKey, + kCTFontUniqueNameKey, // Apple docs say this name is not actually unique + kCTFontFullNameKey, + kCTFontVersionNameKey, + kCTFontPostScriptNameKey, + kCTFontTrademarkNameKey, + kCTFontManufacturerNameKey, + kCTFontDesignerNameKey, + kCTFontDescriptionNameKey, + kCTFontVendorURLNameKey, + kCTFontDesignerURLNameKey, + kCTFontLicenseNameKey, + kCTFontLicenseURLNameKey, + kCTFontSampleTextNameKey, + kCTFontPostScriptCIDNameKey + }; + + const size_t key_count = sizeof(keys)/sizeof(keys[0]); + text_log.Print(L"CTFont Names:\n"); + text_log.PushIndent(); + for (size_t i = 0; i < key_count; i++) + { + CFStringRef appleName = CTFontCopyName(apple_font,keys[i]); + const ON_wString key(keys[i]); + const ON_wString name(appleName); + if (nullptr != appleName) + CFRelease(appleName); + if ( name.IsNotEmpty() ) + { + text_log.Print( + L"\"%ls\" = CTFontCopyName(...,\"%ls\")\n", + static_cast(name), + static_cast(key) + ); + } + } + text_log.PopIndent(); #endif + + text_log.PopIndent(); +} + + +#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) + +NSFont* ON_Font::AppleTollFreeNSFont(CTFontRef appleCTFont) +{ + if (nullptr == appleCTFont) + return nullptr; + NSFont* appleNSFont = (__bridge NSFont*)(appleCTFont); + return appleNSFont; +} + + +CTFontRef AppleTollFreeCTFont(NSFont* appleNSFont) +{ + if (nullptr == appleNSFont) + return nullptr; + CTFontRef appleCTFont = (__bridge CTFontRef)(appleNSFont); + return appleCTFont; +} + +//bool ON_Font::SetFromAppleNSFont( NSFont* apple_font, bool bAnnotationFont ) +//{ +// +// if ( false == ON_FONT_MODIFICATION_PERMITTED ) +// return false; +// +// *this = ON_Font::Unset; +// +// const ON_wString postscript_name = ON_Font::PostScriptNameFromAppleNSFont(apple_font); +// const ON_wString family_name = ON_Font::FamilyNameFromAppleNSFont(apple_font); +// const ON_wString face_name = ON_Font::FaceNameFromAppleNSFont(apple_font); +// +// // Set Windows LOGFONT.lfFaceName to something not empty there is some hope this +// // font might work in Rhino 5 for Windows too. +// // https://mcneel.myjetbrains.com/youtrack/issue/RH-37074 +// const ON_wString windows_logfont_name = family_name; +// +// const bool rc = postscript_name.IsNotEmpty() || family_name.IsNotEmpty(); +// if (rc) +// { +// m_loc_postscript_name = postscript_name; +// m_en_postscript_name = postscript_name; +// m_loc_family_name = family_name; +// m_en_family_name = family_name; +// m_loc_face_name = face_name; +// m_en_face_name = face_name; +// m_loc_windows_logfont_name = windows_logfont_name; +// m_en_windows_logfont_name = windows_logfont_name; +// +// // Can get font metrics from NSFontDescriptor +// // https://developer.apple.com/library/content/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html +// // defaultLineHeight(for theFont: NSFont) +// // fd.xHeight, fd.ascender, fd.descender, fd.capHeight, fd.defaultLineHeightForFont +// +// // Set weight - used if this font needs sustution on another computer +// // https://mcneel.myjetbrains.com/youtrack/issue/RH-37075 +// NSFontDescriptor* fd = apple_font.fontDescriptor; +// NSDictionary* traits = [fd objectForKey: NSFontTraitsAttribute]; +// NSNumber* weightValue = [traits objectForKey: NSFontWeightTrait]; +// if (weightValue) +// { +// const double apple_font_weight_trait = weightValue.doubleValue; +// SetAppleFontWeightTrait(apple_font_weight_trait); +// } +// else if ( 0 != (fd.symbolicTraits & NSFontBoldTrait) ) +// SetFontWeight(ON_Font::Weight::Bold); +// else +// SetFontWeight(ON_Font::Weight::Normal); +// +// // Set style - used if this font needs sustution on another computer +// if ( 0 != (fd.symbolicTraits & NSFontItalicTrait) ) +// m_font_style = ON_Font::Style::Italic; +// else +// m_font_style = ON_Font::Style::Upright; +// +// if ( 0 != (fd.symbolicTraits & NSFontExpandedTrait) ) +// m_font_stretch = ON_Font::Stretch::Expanded; +// else if ( 0 != (fd.symbolicTraits & NSFontCondensedTrait) ) +// m_font_stretch = ON_Font::Stretch::Condensed; +// else +// m_font_stretch = ON_Font::Stretch::Medium; +// +// // Saving point size added January 2018. +// const double point_size = (double)apple_font.pointSize; +// m_point_size +// = (false == bAnnotationFont && ON_Font::IsValidPointSize(point_size) && point_size < ((double)ON_Font::AnnotationFontApplePointSize)) +// ? point_size +// : 0.0; // indicates annotation size (units per em) will be used +// +// m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset; +// +// // do this again because some of the above calls can modify description +// m_loc_postscript_name = postscript_name; +// m_en_postscript_name = m_loc_postscript_name; +// m_loc_family_name = family_name; +// m_en_family_name = m_loc_family_name; +// m_loc_face_name = face_name; +// m_en_face_name = m_loc_face_name; +// m_loc_windows_logfont_name = windows_logfont_name; +// m_en_windows_logfont_name = m_loc_windows_logfont_name; +// +// SetFontOrigin(ON_Font::Origin::AppleFont); +// } +// +// return rc; +//} + +//const ON_wString ON_Font::PostScriptNameFromAppleNSFont( +// NSFont* apple_font +//) +//{ +// if (nullptr == apple_font) +// return ON_wString::EmptyString; +// const ON_String utf8_postscript_name(apple_font.fontName.UTF8String); +// ON_wString postscript_name(utf8_postscript_name); +// postscript_name.TrimLeftAndRight(); +// return postscript_name; +//} + +//const ON_wString ON_Font::FamilyNameFromAppleNSFont( +// NSFont* apple_font +// ) +//{ +// if (nullptr == apple_font) +// return ON_wString::EmptyString; +// const ON_String utf8_family_name(apple_font.familyName.UTF8String); +// ON_wString family_name(utf8_family_name); +// family_name.TrimLeftAndRight(); +// return family_name; +//} + + +//const ON_wString ON_Font::AppleDisplayNameFromAppleNSFont( +// NSFont* apple_font +//) +//{ +// const ON_String utf8_display_name(apple_font.displayName.UTF8String); +// ON_wString display_name(utf8_display_name); +// display_name.TrimLeftAndRight(); +// return display_name; +//} + + +//const ON_wString ON_Font::FaceNameFromAppleNSFont( +// NSFont* apple_font +// ) +//{ +// for(;;) +// { +// if (nullptr == apple_font) +// break; +// const ON_wString family_and_face_name = ON_Font::AppleDisplayNameFromAppleNSFont(apple_font); +// const ON_wString family_name = ON_Font::FamilyNameFromAppleNSFont(apple_font); +// return Internal_FaceNameFromAppleDisplayAndFamilyName(family_and_face_name,family_name); +// } +// +// return ON_wString::EmptyString; +//} + +//NSFont* ON_Font::AppleNSFont() const +//{ +// // Using PointSize() added January 2018. +// const double pointSize +// = ON_Font::IsValidPointSize(m_point_size) +// ? m_point_size +// : 1000.0; // common Apple units per em +// +// NSFont* appleFont = nullptr; +// for(;;) +// { +// appleFont = AppleNSFont(pointSize); +// if ( nullptr == appleFont) +// break; +// +// if ( m_point_size > 0.0 && pointSize == m_point_size) +// break; +// +// const unsigned int upm = CTFontGetUnitsPerEm((CTFontRef)appleFont); +// if (upm <= 0 || fabs(upm - pointSize) <= 0.001 ) +// break; +// +// NSFont* appleFont2 = AppleNSFont(upm); +// if (nullptr != appleFont2) +// return appleFont2; // goal is to have point size = UPM so there is no scaling / rounding in metrics and glyph outlines. +// +// break; +// } +// +// return appleFont; +//} + +//NSFont* ON_Font::AppleNSFont(double pointSize) const +//{ +// const double annotation_font_point_size = (double)ON_Font::Constants::AnnotationFontApplePointSize; +// NSFont* userFont = nullptr; +// +// // TODO - Use ON_Font::InstalledFontFromNames(...) to search installed fonts instead of the following +// +// for (int pass = 0; pass < 2; pass++) +// { +// if ( 0 != pass) +// { +// if ( annotation_font_point_size == pointSize ) +// continue; // already tried this point size +// pointSize = annotation_font_point_size; +// } +// if ( false ==(pointSize > 0.0) ) +// continue; +// +// // TODO - replace the following with ON_Font::InstalledFontFromNames(....) +// +// for(int ps_loc = 0; ps_loc < 2; ps_loc++) +// { +// // NOTE WELL: +// // The post script name is NOT unique for simulated fonts +// // (Bahnschrift and other OpenType variable fonts being one common source of simulated fonts.) +// const ON_wString postscript_name +// = (0 == ps_loc) +// ? m_loc_postscript_name +// : m_en_postscript_name; +// +// if (ps_loc > 0 && postscript_name == m_loc_postscript_name) +// break; +// +// if (postscript_name.IsNotEmpty()) +// { +// // m_apple_font name was a Mac OS font name (NSFont.fontName = Mac OS FontBook "PostScript" name) on some Apple platform device. +// // If the current computer has the same font, this will return the +// // highest fidelity match. +// const ON_String UTF8_postscript_name(postscript_name); +// NSString* fontPostscriptName = [NSString stringWithUTF8String : UTF8_postscript_name]; +// userFont = [NSFont fontWithName : fontPostscriptName size : pointSize]; +// if (userFont) +// break; +// } +// } +// +// // Try getting a NSFont by using NSFontManager +// NSFontTraitMask traitsStyleStretch = 0; +// if (IsItalic()) +// traitsStyleStretch |= NSItalicFontMask; +// if (FontStretch() <= ON_Font::Stretch::Condensed) +// traitsStyleStretch |= NSCondensedFontMask; +// if (FontStretch() >= ON_Font::Stretch::Expanded) +// traitsStyleStretch |= NSExpandedFontMask; +// +// ON_String family_name = FamilyName(); // convert to UTF8 +// NSString* fontFamilyName = [NSString stringWithUTF8String : family_name]; // font family name +// +// // https://developer.apple.com/documentation/appkit/nsfontmanager/1462332-fontwithfamily +// // weight +// // A hint for the weight desired, on a scale of 0 to 15: +// // a value of 5 indicates a normal or book weight, +// // and 9 or more a bold or heavier weight. +// // The weight is ignored if fontTraitMask includes NSBoldFontMask. +// +// int weightFromFontWeight; +// switch (FontWeight()) +// { +// case ON_Font::Weight::Thin: +// weightFromFontWeight = 2; +// break; +// case ON_Font::Weight::Ultralight: +// weightFromFontWeight = 3; +// break; +// case ON_Font::Weight::Light: +// weightFromFontWeight = 4; +// break; +// case ON_Font::Weight::Normal: +// weightFromFontWeight = 5; +// break; +// case ON_Font::Weight::Medium: +// weightFromFontWeight = 6; +// break; +// case ON_Font::Weight::Semibold: +// weightFromFontWeight = 7; +// break; +// case ON_Font::Weight::Bold: +// weightFromFontWeight = 9; +// break; +// case ON_Font::Weight::Ultrabold: +// weightFromFontWeight = 12; +// break; +// case ON_Font::Weight::Heavy: +// weightFromFontWeight = 15; +// break; +// default: +// weightFromFontWeight = 5; +// break; +// } +// NSFontTraitMask traitsStyleStretchWeight +// = IsBoldInQuartet() +// ? (traitsStyleStretch | NSBoldFontMask) +// : (traitsStyleStretch | NSUnboldFontMask); +// userFont = [[NSFontManager sharedFontManager] fontWithFamily:fontFamilyName traits : traitsStyleStretchWeight weight : weightFromFontWeight size : pointSize]; +// if (userFont) +// break; +// +// userFont = [[NSFontManager sharedFontManager] fontWithFamily:fontFamilyName traits : traitsStyleStretch weight : weightFromFontWeight size : pointSize]; +// if (userFont) +// break; +// +// if ( 5 != weightFromFontWeight) +// { +// userFont = [[NSFontManager sharedFontManager] fontWithFamily:fontFamilyName traits : traitsStyleStretch weight : 5 size : pointSize]; +// if (userFont) +// break; +// } +// +// // Try using just FontFaceName() +// userFont = [NSFont fontWithName : fontFamilyName size : pointSize]; +// if (userFont) +// break; +// +// // Cannot find an equivalent font. Just use a system font. +// userFont = [NSFont userFontOfSize : pointSize]; +// if (userFont) +// break; +// +// userFont = [NSFont systemFontOfSize : pointSize]; +// if (userFont) +// break; +// } +// +// return userFont; +//} + +//void ON_Font::DumpNSFont( +// NSFont* apple_font, +// ON_TextLog& text_log +//) +//{ +// if (nullptr == apple_font) +// { +// text_log.Print("NSFont = nullptr\n"); +// return; +// } +// +// text_log.Print("NSFont\n"); +// text_log.PushIndent(); +// +// const ON_wString postscript_name = ON_Font::PostScriptNameFromAppleNSFont(apple_font); +// text_log.Print(L"NSFont.fontName: \"%ls\"\n",static_cast(postscript_name)); +// +// const ON_wString display_name = ON_Font::AppleDisplayNameFromAppleNSFont(apple_font); +// text_log.Print(L"NSFont.displayName: \"%ls\"\n",static_cast(display_name)); +// +// const ON_wString family_name = ON_Font::FamilyNameFromAppleNSFont(apple_font); +// text_log.Print(L"NSFont.familyName: \"%ls\"\n",static_cast(family_name)); +// +// // Apple NSFont and MacOS do not have "face names" as a font attribute. +// // This is the "typeface" name shown in Apple FontBook +// const ON_wString fake_face_name = ON_Font::FaceNameFromAppleNSFont(apple_font); +// text_log.Print(L"NSFont FontBook typeface: \"%ls\"\n",static_cast(fake_face_name)); +// +// const double point_size = (double)apple_font.pointSize; +// text_log.Print("NSFont.pointSize: %g\n",point_size); +// text_log.PopIndent(); +//} + +#endif + #endif diff --git a/opennurbs_apple_nsfont.h b/opennurbs_apple_nsfont.h index 816c5ab3..7318c80d 100644 --- a/opennurbs_apple_nsfont.h +++ b/opennurbs_apple_nsfont.h @@ -16,20 +16,25 @@ #if !defined(OPENNURBS_APPLE_NSFONT_INC_) #define OPENNURBS_APPLE_NSFONT_INC_ -//#define ON_NSFONT_GLYPH_SUPPORT_WIP -#if defined(ON_RUNTIME_APPLE) && defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) && defined(ON_NSFONT_GLYPH_SUPPORT_WIP) +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) ON_DECL -bool ON_AppleNSFontGetGlyphMetrics( - NSFont* appleFont, +unsigned int ON_AppleFontGlyphIndex( + CTFontRef appleFont, + unsigned int unicode_code_point +); + +ON_DECL +bool ON_AppleFontGetGlyphMetrics( + CTFontRef appleFont, unsigned int font_design_units_per_M, unsigned int glyphIndex, class ON_TextBox& glyph_metrics ); ON_DECL -bool ON_AppleNSFontGetGlyphOutline( - NSFont* appleFont, +bool ON_AppleFontGetGlyphOutline( + CTFontRef appleFont, unsigned int font_design_units_per_M, unsigned int glyphIndex, ON_OutlineFigure::Type figure_type, diff --git a/opennurbs_archive.cpp b/opennurbs_archive.cpp index e4ff7e21..a8366250 100644 --- a/opennurbs_archive.cpp +++ b/opennurbs_archive.cpp @@ -559,7 +559,6 @@ ON_BinaryArchive::eStorageDeviceError ON_BinaryArchive::StorageDeviceErrorFromUn return ON_BinaryArchive::eStorageDeviceError::UnknownDeviceError; } - unsigned int ON_BinaryArchive::StorageDeviceError() const { return m_storage_device_error; @@ -15667,6 +15666,9 @@ bool ON_BinaryArchive::BeginWrite3dmUserTable( ON_ERROR("ON_BinaryArchive::BeginWrite3dmUserTable() - nil usertable_uuid not permitted."); return false; } + + if (false == ArchiveContains3dmTable(ON_3dmArchiveTableType::user_table)) + return false; if (false == ShouldSerializeUserDataItem(plugin_id, plugin_id)) return false; @@ -16228,14 +16230,47 @@ unsigned int ON_BinaryArchive::ErrorMessageMask() const bool ON_BinaryArchive::MaskReadError( ON__UINT64 sizeof_request, ON__UINT64 sizeof_read ) const { + if ( 0 == (static_cast(m_mode) % 2 ) ) + return false; // something is seriously wrong + if ( sizeof_request == sizeof_read ) return true; // no error - if ( sizeof_request > sizeof_read ) + + if ( sizeof_read > sizeof_request ) return false; // something is seriously wrong + if ( 0 != (0x04 & m_error_message_mask) ) return true; - if ( 0 != (0x01 & m_error_message_mask) && 4 == sizeof_request && 0 == sizeof_read ) + + if (0 != (0x01 & m_error_message_mask) && 4 == sizeof_request && 0 == sizeof_read) + { + // when reading v1 files, there are some situations where + // it is reasonable to attempt to read 4 bytes at the end + // of a file. return true; + } + + if (0 == m_3dm_version + && 0 == m_3dm_opennurbs_version + && 0 == m_3dm_start_section_offset + && ON_3dmArchiveTableType::Unset == m_3dm_previous_table + && ON_3dmArchiveTableType::Unset == m_3dm_active_table + && ON_3dmArchiveTableType::Unset == m_3dm_first_failed_table + && 0 == m_chunk + && ON::archive_mode::read3dm == m_mode + ) + { + // In Read3dmStartSection(), we search for the string + // "3D Geometry File Format ...". When a non-.3dm file + // is searched, we eventually reach the end of the file. + // This error condition is reported by the returning + // false from ON_BinaryArchive::Read3dmStartSection(). + // ON_ERROR is not called to prevent annoying everyone + // when the open file dialog is digging around looking + // for files. + return true; + } + return false; // parial read not permitted at this time. } @@ -16390,47 +16425,11 @@ size_t ON_BinaryArchive::Read( size_t count, void* p ) { UpdateCRC(count, p); } - else + else if ( false == MaskReadError(count,readcount) ) { - // see if this is an error condition - for (;;) - { - if (0 != (m_error_message_mask & 0x01) - && 0 == readcount && 4 == count - ) - { - // when reading v1 files, there are some situations where - // it is reasonable to attempt to read 4 bytes at the end - // of a file. - break; - } - - if (0 == m_3dm_version - && 0 == m_3dm_opennurbs_version - && 0 == m_3dm_start_section_offset - && ON_3dmArchiveTableType::Unset == m_3dm_previous_table - && ON_3dmArchiveTableType::Unset == m_3dm_active_table - && ON_3dmArchiveTableType::Unset == m_3dm_first_failed_table - && 0 == m_chunk - && ON::archive_mode::read3dm == m_mode - ) - { - // In Read3dmStartSection(), we search for the string - // "3D Geometry File Format ...". When a non-.3dm file - // is searched, we eventually reach the end of the file. - // This error condition is reported by the returning - // false from ON_BinaryArchive::Read3dmStartSection(). - // ON_ERROR is not called to prevent annoying everyone - // when the open file dialog is digging around looking - // for files. - break; - } - - // error occured - SetStorageDeviceError(ON_BinaryArchive::eStorageDeviceError::ReadFailed); - ON_ERROR("Internal_ReadOverride(count, p) failed."); - break; - } + // error occured + SetStorageDeviceError(ON_BinaryArchive::eStorageDeviceError::ReadFailed); + ON_ERROR("Internal_ReadOverride(count, p) failed."); } if (readcount > 0) @@ -16585,7 +16584,10 @@ size_t ON_BinaryFile::Internal_ReadOverride( size_t count, void* p ) const size_t rc = (m_fp) ? fread( p, 1, count, m_fp ) : 0; if (rc != count && nullptr != m_fp) { - ON_ERROR("fread() failed."); + if (false == MaskReadError(count, rc)) + { + ON_ERROR("fread() failed."); + } } return rc; } diff --git a/opennurbs_archive.h b/opennurbs_archive.h index 20e33f21..c00c80ef 100644 --- a/opennurbs_archive.h +++ b/opennurbs_archive.h @@ -3754,8 +3754,8 @@ public: plug-in wrote the user table. goo - [in] Returns: - True if the goo was written or skipped because it could not be robustly - saved. False if a catastrophic IO error occured. + True if the goo was written. + False if skipped because it could not be robustly saved. */ bool Write3dmAnonymousUserTableRecord( ON_UUID plugin_id, @@ -4312,7 +4312,14 @@ private: actual number of bytes read (like fread()) */ size_t Read(size_t, void*); + protected: + /* + Remarks: + In some unusual situations when reading old or damaged files, a read may fail. + Call MaskReadError( ON__UINT64 sizeof_request, ON__UINT64 sizeof_read ) + before calling ON_ERROR(). + */ virtual size_t Internal_ReadOverride( size_t, void* ) = 0; private: diff --git a/opennurbs_array_defs.h b/opennurbs_array_defs.h index 18497222..e65aa6e7 100644 --- a/opennurbs_array_defs.h +++ b/opennurbs_array_defs.h @@ -985,13 +985,7 @@ ON_ObjectArray& ON_ObjectArray::operator=( ON_ObjectArray&& src ) { if( this != &src ) { - this->Destroy(); - m_a = src.m_a; - m_count = src.m_count; - m_capacity = src.m_capacity; - src.m_a = 0; - src.m_count = 0; - src.m_capacity = 0; + ON_ClassArray::operator=(std::move(src)); } return *this; } diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp index f8455a3c..d1a52b0e 100644 --- a/opennurbs_brep.cpp +++ b/opennurbs_brep.cpp @@ -4036,8 +4036,290 @@ bool CheckLoopOnSrfHelper( const ON_Brep& brep, } +static void Internal_ValidateBrepIndex( + ON__UINT_PTR text_log_ptr_plus, + const wchar_t* corruption_descirption, + bool& bCorrupt, + const int max_idx, + const int& idx +) +{ + if (idx < max_idx) + return; + const bool bSilentError = (0 != (text_log_ptr_plus & 1)); + const bool bRepair = (0 != (text_log_ptr_plus & 2)); + ON_TextLog* text_log = (ON_TextLog*)(text_log_ptr_plus & (~((ON__UINT_PTR)3)) ); + if (false == bCorrupt && false == bSilentError) + { + ON_ERROR("ON_Brep has corrupt indices that will cause crashes."); + } + bCorrupt = true; + if (nullptr != text_log) + text_log->PrintString(corruption_descirption); + if (bRepair) + const_cast(idx) = -1; // prevents crashes because code checks for -1 as an unset index value +} + + +static void Internal_ValidateBrepIndexArray( + ON__UINT_PTR text_log_ptr_plus, + const wchar_t* corruption_descirption, + bool& bCorrupt, + const int max_idx, + const int idex_count, + const int* idex_a +) +{ + for (int j = 0; j < idex_count; j++) + { + Internal_ValidateBrepIndex(text_log_ptr_plus, corruption_descirption, bCorrupt, max_idx, idex_a[j]); + } +} + + +static void Internal_ValidateBrepIndexSimpleArray( + ON__UINT_PTR text_log_ptr_plus, + const wchar_t* corruption_descirption, + bool& bCorrupt, + const int max_idx, + const ON_SimpleArray& idx_array +) +{ + const int idex_count = idx_array.Count(); + const int* idex_a = idx_array.Array(); + Internal_ValidateBrepIndexArray(text_log_ptr_plus, corruption_descirption, bCorrupt, max_idx, idex_count, idex_a); +} + +static void Internal_ValidateBrepComponentBackPtr( + ON__UINT_PTR text_log_ptr_plus, + const wchar_t* corruption_descirption, + bool& bCorrupt, + const ON_Brep* this_brep, + ON_Brep*const* brep_back_ptr, + const int idx, + const int& component_back_idx +) +{ + if (this_brep != *brep_back_ptr) + { + const bool bSilentError = (0 != (text_log_ptr_plus & 1)); + const bool bRepair = (0 != (text_log_ptr_plus & 2)); + ON_TextLog* text_log = (ON_TextLog*)(text_log_ptr_plus & (~((ON__UINT_PTR)3)) ); + + if (false == bCorrupt && false == bSilentError) + { + ON_ERROR("ON_Brep has corrupt indices that will cause crashes."); + } + bCorrupt = true; + if (nullptr != text_log) + text_log->Print(corruption_descirption); + if (bRepair) + *const_cast(brep_back_ptr) = const_cast(this_brep); + } + + if (idx != component_back_idx) + { + const bool bSilentError = (0 != (text_log_ptr_plus & 1)); + const bool bRepair = (0 != (text_log_ptr_plus & 2)); + ON_TextLog* text_log = (ON_TextLog*)(text_log_ptr_plus & (~((ON__UINT_PTR)3))); + + if (false == bCorrupt && false == bSilentError) + { + ON_ERROR("ON_Brep has corrupt indices that will cause crashes."); + } + bCorrupt = true; + if (nullptr != text_log) + text_log->Print(corruption_descirption); + if (bRepair) + const_cast(component_back_idx) = idx; + } +} + +bool ON_Brep::IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log +) const +{ + bool bCorrupt = false; + + const int C2_count = m_C2.Count(); + const int C3_count = m_C3.Count(); + const int S_count = m_S.Count(); + const int V_count = m_V.Count(); + const int E_count = m_E.Count(); + const int T_count = m_T.Count(); + const int L_count = m_L.Count(); + const int F_count = m_F.Count(); + + ON__UINT_PTR text_log_ptr = (ON__UINT_PTR)text_log; + if (bSilentError) + text_log_ptr |= 1; + if (bRepair) + text_log_ptr |= 2; + + ON_Brep* ignored_this_ptr = const_cast(this); + + for (int vi = 0; vi < V_count; vi++) + { + const ON_BrepVertex& v = m_V[vi]; + Internal_ValidateBrepComponentBackPtr( + text_log_ptr, + L"Corrupt ON_BrepVertex.m_vertex_index back pointer.\n", + bCorrupt, + this, &ignored_this_ptr, + vi, v.m_vertex_index + ); + + Internal_ValidateBrepIndexSimpleArray( + text_log_ptr, + L"Corrupt ON_BrepVertex.m_ei[] index.\n", + bCorrupt, + E_count, + v.m_ei + ); + } + + for (int ei = 0; ei < E_count; ei++) + { + const ON_BrepEdge& e = m_E[ei]; + Internal_ValidateBrepComponentBackPtr( + text_log_ptr, + L"Corrupt ON_BrepEdge m_brep or m_edge_index back pointers.\n", + bCorrupt, + this, &e.m_brep, + ei, e.m_edge_index + ); + + Internal_ValidateBrepIndexArray( + text_log_ptr, + L"Corrupt ON_BrepEdge.m_vi[] index.\n", + bCorrupt, + V_count, + 2, + e.m_vi + ); + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepEdge.m_c3i index.\n", + bCorrupt, + C3_count, + e.m_c3i + ); + Internal_ValidateBrepIndexSimpleArray( + text_log_ptr, + L"Corrupt ON_BrepEdge.m_ti[] index.\n", + bCorrupt, + T_count, + e.m_ti + ); + } + + for (int ti = 0; ti < T_count; ti++) + { + const ON_BrepTrim& t = m_T[ti]; + Internal_ValidateBrepComponentBackPtr( + text_log_ptr, + L"Corrupt ON_BrepTrim m_brep or m_trim_index back pointers.\n", + bCorrupt, + this, &t.m_brep, + ti, t.m_trim_index + ); + + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepTrim.m_c2i index.\n", + bCorrupt, + C2_count, + t.m_c2i + ); + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepTrim.m_ei index.\n", + bCorrupt, + E_count, + t.m_ei + ); + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepTrim.m_li index.\n", + bCorrupt, + L_count, + t.m_li + ); + Internal_ValidateBrepIndexArray( + text_log_ptr, + L"Corrupt ON_BrepTrim.m_vi[] index.\n", + bCorrupt, + V_count, + 2, + t.m_vi + ); + } + + for (int li = 0; li < L_count; li++) + { + const ON_BrepLoop& l = m_L[li]; + Internal_ValidateBrepComponentBackPtr( + text_log_ptr, + L"Corrupt ON_BrepLoop m_brep or m_loop_index back pointers.\n", + bCorrupt, + this, &l.m_brep, + li, l.m_loop_index + ); + + Internal_ValidateBrepIndexSimpleArray( + text_log_ptr, + L"Corrupt ON_BrepLoop.m_ti[] index.\n", + bCorrupt, + T_count, + l.m_ti + ); + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepLoop.m_fi index.\n", + bCorrupt, + F_count, + l.m_fi + ); + } + + for (int fi = 0; fi < F_count; fi++) + { + const ON_BrepFace& f = m_F[fi]; + Internal_ValidateBrepComponentBackPtr( + text_log_ptr, + L"Corrupt ON_BrepFace m_brep or m_face_index back pointers.\n", + bCorrupt, + this, &f.m_brep, + fi, f.m_face_index + ); + + Internal_ValidateBrepIndexSimpleArray( + text_log_ptr, + L"Corrupt ON_BrepFace.m_li[] index.\n", + bCorrupt, + L_count, + f.m_li + ); + Internal_ValidateBrepIndex( + text_log_ptr, + L"Corrupt ON_BrepFace.m_si index.\n", + bCorrupt, + S_count, + f.m_si + ); + } + + return bCorrupt; +} + + bool ON_Brep::IsValid( ON_TextLog* text_log ) const { + if (IsCorrupt(false, true, text_log)) + return false; + const int curve2d_count = m_C2.Count(); const int curve3d_count = m_C3.Count(); const int surface_count = m_S.Count(); @@ -8442,67 +8724,84 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD ON_Object* dup = 0; // mark vertices, edges, faces, surfaces, and curves to duplicate - ON_SimpleArray s_remap(m_S.Count()); - s_remap.SetCount(m_S.Count()); + const int s_ct = m_S.Count(); + ON_SimpleArray s_remap(s_ct); + s_remap.SetCount(s_ct); s_remap.Zero(); - ON_SimpleArray f_remap(m_F.Count()); - f_remap.SetCount(m_F.Count()); + + const int f_ct = m_F.Count(); + ON_SimpleArray f_remap(f_ct); + f_remap.SetCount(f_ct); f_remap.Zero(); - ON_SimpleArray c2_remap(m_C2.Count()); - c2_remap.SetCount(m_C2.Count()); + + const int c2_ct = m_C2.Count(); + ON_SimpleArray c2_remap(c2_ct); + c2_remap.SetCount(c2_ct); c2_remap.Zero(); - ON_SimpleArray c3_remap(m_C3.Count()); - c3_remap.SetCount(m_C3.Count()); + + const int c3_ct = m_C3.Count(); + ON_SimpleArray c3_remap(c3_ct); + c3_remap.SetCount(c3_ct); c3_remap.Zero(); - ON_SimpleArray e_remap(m_E.Count()); - e_remap.SetCount(m_E.Count()); + + const int e_ct = m_E.Count(); + ON_SimpleArray e_remap(e_ct); + e_remap.SetCount(e_ct); e_remap.Zero(); - ON_SimpleArray v_remap(m_V.Count()); - v_remap.SetCount(m_V.Count()); + + const int v_ct = m_V.Count(); + ON_SimpleArray v_remap(v_ct); + v_remap.SetCount(v_ct); v_remap.Zero(); - for (i = 0; i < face_count; i++ ) { + + const int t_ct = m_T.Count(); + const int l_ct = m_L.Count(); + + for (i = 0; i < face_count; i++ ) + { fi = face_index[i]; - if (fi >= 0 && fi < m_F.Count() ) { + if (fi >= 0 && fi < f_ct ) + { const ON_BrepFace& face = m_F[fi]; rc = true; f_remap[fi] = 1; si = face.m_si; - if ( si >= 0 && si < m_S.Count() ) { + if ( si >= 0 && si < s_ct ) + { s_remap[si] = 1; } for ( fli = 0; fli < face.m_li.Count(); fli++ ) { li = face.m_li[fli]; - if ( li < 0 || li >= m_L.Count() ) + if ( li < 0 || li >= l_ct ) continue; const ON_BrepLoop& loop = m_L[li]; for ( lti = 0; lti < loop.m_ti.Count(); lti++ ) { ti = loop.m_ti[lti]; - if ( ti < 0 || ti >= m_T.Count() ) + if ( ti < 0 || ti >= t_ct ) continue; const ON_BrepTrim& trim = m_T[ti]; - if ( trim.m_ei >= 0 && trim.m_ei < m_E.Count() ) + if ( trim.m_ei >= 0 && trim.m_ei < e_ct ) { int vi; e_remap[trim.m_ei] = 1; vi = m_E[trim.m_ei].m_vi[0]; - if ( vi >= 0 ) + if ( vi >= 0 && v_ct > vi) v_remap[vi] = 1; vi = m_E[trim.m_ei].m_vi[1]; - if ( vi >= 0 ) + if ( vi >= 0 && v_ct > vi) v_remap[vi] = 1; } - if ( trim.m_vi[0] >= 0 ) + if ( trim.m_vi[0] >= 0 && v_ct > trim.m_vi[0]) v_remap[trim.m_vi[0]] = 1; - if ( trim.m_vi[1] >= 0 ) + if ( trim.m_vi[1] >= 0 && v_ct > trim.m_vi[1]) v_remap[trim.m_vi[1]] = 1; int ci = trim.EdgeCurveIndexOf(); - if ( ci >= 0 ) { + if ( ci >= 0 && c3_ct > ci) c3_remap[ci] = 1; - } ci = trim.TrimCurveIndexOf(); - if ( ci >= 0 ) + if ( ci >= 0 && c2_ct > ci) c2_remap[ci] = 1; } } @@ -8514,7 +8813,7 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD brep_copy = new ON_Brep(); // duplicate surfaces - for ( i = 0; i < m_S.Count() && rc; i++ ) + for ( i = 0; i < s_ct && rc; i++ ) { if ( s_remap[i] ) { if ( !m_S[i] ) @@ -8529,10 +8828,10 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD else s_remap[i] = -1; } - rc = ( rc && i == m_S.Count() ); + rc = ( rc && i == s_ct ); // duplicate 2d curves - for ( i = 0; i < m_C2.Count() && rc; i++ ) + for ( i = 0; i < c2_ct && rc; i++ ) { if ( c2_remap[i] ) { if ( !m_C2[i] ) @@ -8547,10 +8846,10 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD else c2_remap[i] = -1; } - rc = ( rc && i == m_C2.Count() ); + rc = ( rc && i == c2_ct ); // duplicate 3d curves - for ( i = 0; i < m_C3.Count() && rc; i++ ) + for ( i = 0; i < c3_ct && rc; i++ ) { if ( c3_remap[i] ) { if ( !m_C3[i] ) @@ -8565,10 +8864,10 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD else c3_remap[i] = -1; } - rc = ( rc && i == m_C3.Count() ); + rc = ( rc && i == c3_ct ); // duplicate vertices - for (i = 0; i < m_V.Count() && rc; i++ ) + for (i = 0; i < v_ct && rc; i++ ) { if (v_remap[i]) { @@ -8580,10 +8879,10 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD else v_remap[i] = -1; } - rc = ( rc && i == m_V.Count() ); + rc = ( rc && i == v_ct ); // duplicate edges - for (i = 0; i < m_E.Count() && rc; i++ ) + for (i = 0; i < e_ct && rc; i++ ) { if (e_remap[i]) { @@ -8610,7 +8909,7 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD else e_remap[i] = -1; } - rc = ( rc && i == m_E.Count() ); + rc = ( rc && i == e_ct ); //03/11/2010 Tim //More checking to prevent crashes @@ -8618,7 +8917,7 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD bool bFoundBadIdx = false; // duplicate faces - for ( fi = 0; rc && fi < m_F.Count() && rc && !bFoundBadIdx; fi++ ) + for ( fi = 0; rc && fi < f_ct && rc && !bFoundBadIdx; fi++ ) { if ( f_remap[fi] == 0 ) continue; @@ -8626,7 +8925,7 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD const ON_BrepFace& face = m_F[fi]; // duplicate face - si = (face.m_si>=0) ? s_remap[face.m_si] : -1; + si = (face.m_si>=0 && s_ct > face.m_si) ? s_remap[face.m_si] : -1; if ( si < 0 ) break; @@ -8644,7 +8943,7 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD for ( fli = 0; fli < face.m_li.Count() && !bFoundBadIdx; fli++ ) { li = face.m_li[fli]; - if (0 > li || m_L.Count() <= li) + if (0 > li || l_ct <= li) { bFoundBadIdx = true; break; @@ -8657,20 +8956,22 @@ ON_Brep* ON_Brep::DuplicateFaces( int face_count, const int* face_index, bool bD for ( lti = 0; lti < loop.m_ti.Count() && !bFoundBadIdx; lti++ ) { ti = loop.m_ti[lti]; - if (0 > ti || m_T.Count() <= ti) + if (0 > ti || t_ct <= ti) { bFoundBadIdx = true; break; } const ON_BrepTrim& trim = m_T[ti]; - i = (trim.m_c2i>=0) ? c2_remap[trim.m_c2i] : -1; - if ( trim.m_ei >= 0 ) { + i = (trim.m_c2i >= 0 && c2_ct > trim.m_c2i) ? c2_remap[trim.m_c2i] : -1; + if ( trim.m_ei >= 0 && e_ct > trim.m_ei) + { i = brep_copy->NewTrim( brep_copy->m_E[e_remap[trim.m_ei]], trim.m_bRev3d, loop_copy, i ).m_trim_index; } - else { + else + { i = brep_copy->NewTrim( trim.m_bRev3d, loop_copy, i ).m_trim_index; - int vi0 = (trim.m_vi[0]>=0) ? v_remap[trim.m_vi[0]] : -1; - int vi1 = (trim.m_vi[1]>=0) ? v_remap[trim.m_vi[1]] : -1; + int vi0 = (trim.m_vi[0] >= 0 && v_ct > trim.m_vi[0]) ? v_remap[trim.m_vi[0]] : -1; + int vi1 = (trim.m_vi[1] >= 0 && v_ct > trim.m_vi[1]) ? v_remap[trim.m_vi[1]] : -1; brep_copy->m_T[i].m_vi[0] = vi0; brep_copy->m_T[i].m_vi[1] = vi1; } @@ -9579,6 +9880,10 @@ ON_Brep& ON_Brep::operator=(const ON_Brep& src) src.m_C3.Duplicate( m_C3 ); src.m_S.Duplicate( m_S ); + const int C2_count = m_C2.Count(); + const int C3_count = m_C3.Count(); + const int S_count = m_S.Count(); + int i, count = m_V.Count(); for ( i = 0; i < count; i++ ) { @@ -9592,6 +9897,12 @@ ON_Brep& ON_Brep::operator=(const ON_Brep& src) ON_BrepEdge& e = m_E[i]; e.m_brep = this; + if (e.m_c3i >= C3_count) + { + ON_ERROR("src brep has invalid ON_BrepEdge.m_c3i value."); + e.m_c3i = -1; + } + // update curve proxy info to point at 3d curve in this brep e.SetProxyCurve( ( e.m_c3i >= 0 ) ? m_C3[e.m_c3i] : 0, src.m_E[i].ProxyCurveDomain() @@ -9613,6 +9924,13 @@ ON_Brep& ON_Brep::operator=(const ON_Brep& src) m_F[i] = src.m_F[i]; ON_BrepFace& f = m_F[i]; f.m_brep = this; + + if (f.m_si >= S_count) + { + ON_ERROR("src brep has invalid ON_BrepFace.m_si value."); + f.m_si = -1; + } + // update surface proxy info to point at 3d surface in this brep f.SetProxySurface(( f.m_si >= 0 ) ? m_S[f.m_si] : 0); f.m_bbox = src.m_F[i].m_bbox; // because SetProxySurface destroys it @@ -9625,6 +9943,12 @@ ON_Brep& ON_Brep::operator=(const ON_Brep& src) ON_BrepTrim& trim = m_T[i]; trim.m_brep = this; + if (trim.m_c2i >= C2_count) + { + ON_ERROR("src brep has invalid ON_BrepTrim.m_c2i value."); + trim.m_c2i = -1; + } + // update curve proxy info to point at 2d curve in this brep trim.SetProxyCurve( ( trim.m_c2i >= 0 ) ? m_C2[trim.m_c2i] : 0, src.m_T[i].ProxyCurveDomain() diff --git a/opennurbs_brep.h b/opennurbs_brep.h index 462478f8..d696e65e 100644 --- a/opennurbs_brep.h +++ b/opennurbs_brep.h @@ -1735,6 +1735,28 @@ public: ON_SumSurface*& pSumSurface ); + /* + Description: + Check for corrupt data values that are likely to cause crashes. + Parameters: + bRepair - [in] + If true, const_cast<> will be used to change the corrupt data + so that crashes are less likely. + bSilentError - [in] + If true, ON_ERROR will not be called when corruption is detected. + text_log - [out] + If text_log is not null, then a description of corruption + is printed using text_log. + Remarks: + Ideally, IsCorrupt() would be a virtual function on ON_Object, + but doing that at this point would break the public SDK. + */ + bool IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log + ) const; + bool IsValid( class ON_TextLog* text_log = nullptr ) const override; /* diff --git a/opennurbs_defines.cpp b/opennurbs_defines.cpp index d913bf17..6bdb4980 100644 --- a/opennurbs_defines.cpp +++ b/opennurbs_defines.cpp @@ -2343,6 +2343,43 @@ int ON_2udex::DictionaryCompare( return 0; } +int ON_2udex::CompareFirstIndex( + const ON_2udex* lhs, + const ON_2udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->i < rhs->i) + return -1; + if (lhs->i > rhs->i) + return 1; + return 0; +} + +int ON_2udex::CompareSecondIndex( + const ON_2udex* lhs, + const ON_2udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->j < rhs->j) + return -1; + if (lhs->j > rhs->j) + return 1; + return 0; +} + + ON_3udex::ON_3udex( unsigned int iValue, unsigned int jValue, @@ -2352,6 +2389,108 @@ ON_3udex::ON_3udex( , k(kValue) {} +int ON_3udex::DictionaryCompare( + const ON_3udex* lhs, + const ON_3udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->i < rhs->i) + return -1; + if (lhs->i > rhs->i) + return 1; + if (lhs->j < rhs->j) + return -1; + if (lhs->j > rhs->j) + return 1; + if (lhs->k < rhs->k) + return -1; + if (lhs->k > rhs->k) + return 1; + return 0; +} + +int ON_3udex::CompareFirstIndex( + const ON_3udex* lhs, + const ON_3udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->i < rhs->i) + return -1; + if (lhs->i > rhs->i) + return 1; + return 0; +} + +int ON_3udex::CompareSecondIndex( + const ON_3udex* lhs, + const ON_3udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->j < rhs->j) + return -1; + if (lhs->j > rhs->j) + return 1; + return 0; +} + +int ON_3udex::CompareThirdIndex( + const ON_3udex* lhs, + const ON_3udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->k < rhs->k) + return -1; + if (lhs->k > rhs->k) + return 1; + return 0; +} + +int ON_3udex::CompareFirstAndSecondIndex( + const ON_3udex* lhs, + const ON_3udex* rhs +) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->i < rhs->i) + return -1; + if (lhs->i > rhs->i) + return 1; + if (lhs->j < rhs->j) + return -1; + if (lhs->j > rhs->j) + return 1; + return 0; +} + ON_4udex::ON_4udex( unsigned int iValue, unsigned int jValue, diff --git a/opennurbs_defines.h b/opennurbs_defines.h index b8334357..64aa3272 100644 --- a/opennurbs_defines.h +++ b/opennurbs_defines.h @@ -464,6 +464,16 @@ public: const ON_2udex* rhs ); + static int CompareFirstIndex( + const ON_2udex* lhs, + const ON_2udex* rhs + ); + + static int CompareSecondIndex( + const ON_2udex* lhs, + const ON_2udex* rhs + ); + static const ON_2udex Unset; // (ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX); static const ON_2udex Zero; // (0, 0) }; @@ -506,6 +516,32 @@ public: static const ON_3udex Unset; // (ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX); static const ON_3udex Zero; // (0, 0, 0) + + static int DictionaryCompare( + const ON_3udex* lhs, + const ON_3udex* rhs + ); + + static int CompareFirstIndex( + const ON_3udex* lhs, + const ON_3udex* rhs + ); + + static int CompareSecondIndex( + const ON_3udex* lhs, + const ON_3udex* rhs + ); + + static int CompareThirdIndex( + const ON_3udex* lhs, + const ON_3udex* rhs + ); + + static int CompareFirstAndSecondIndex( + const ON_3udex* lhs, + const ON_3udex* rhs + ); + }; // quadruplet of integer indices. @@ -567,6 +603,47 @@ enum class ON_StringMapOrdinalType : int MinimumOrdinal = 3 }; +enum class ON_DateFormat : int +{ + Unset = 0, + Omit = 1, + + /// + /// February 1st, 2001 = 2001-2-1 + /// + YearMonthDay = 2, + + /// + /// February 1st, 2001 = 2001-1-2 + /// + YearDayMonth = 3, + + /// + /// February 1st, 2001 = 2-1-2001 + /// + MonthDayYear = 4, + + /// + /// February 1st, 2001 = 1-2-2001 + /// + DayMonthYear = 5, + + /// + /// February 1st, 2001 = 2001-32 + /// + YearDayOfYear = 6 +}; + +enum class ON_TimeFormat : int +{ + Unset = 0, + Omit = 1, + HourMinute12 = 2, + HourMinuteSecond12 = 3, + HourMinute24 = 4, + HourMinuteSecond24 = 5 +}; + ON_DECL ON_StringMapOrdinalType ON_StringMapOrdinalTypeFromStringMapType( ON_StringMapType map_type @@ -622,6 +699,13 @@ public: static unsigned int VersionMajor(); + /* + Returns: + 63 = maximum major version number that opennurbs version number utilities can handle. + */ + static + unsigned int VersionMajorMaximum(); + /* Returns: The value of OPENNURBS_VERSION_MINOR, which is defined in opennurbs_version.h @@ -630,6 +714,13 @@ public: static unsigned int VersionMinor(); + /* + Returns: + 127 = maximum minor version number that opennurbs version number utilities can handle. + */ + static + unsigned int VersionMinorMaximum(); + /* Returns: The value of OPENNURBS_VERSION_YEAR, which is defined in opennurbs_version.h diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp index 83b6b1a0..4c1875f3 100644 --- a/opennurbs_dimension.cpp +++ b/opennurbs_dimension.cpp @@ -674,6 +674,13 @@ bool ON_DimLinear::GetTextXform( bool text_outside = false; double dist = Measurement(); + // Display scale for detail viewports when page space dimension measures model space + if (ON_nil_uuid != DetailMeasured()) + { + double dist_scale = DistanceScale(); + if (dist_scale != 1.0 && dist_scale > 0.0) + dist /= dist_scale; + } // V6_Dimstyle Arrow1 & Arrow2 double asz = dimstyle->ArrowSize() * dimscale; @@ -685,6 +692,11 @@ bool ON_DimLinear::GetTextXform( total_text_width = 0.0; text_outside = true; } + else if (force_text == ON_Dimension::ForceText::Inside) + { + total_text_width = 0.0; + text_outside = false; + } else if (0.0 < total_text_width) total_text_width += text_gap; @@ -1655,12 +1667,14 @@ static int ClipArcToTextRect( v0 = v1; } - double s[4]; + double s[8]; int intcount = 0; for (int i = 0; i < 4; i++) { ON_3dPoint p[2]; int icount = ON_Intersect(frust_plane[i], dimarc, p[0], p[1]); + if (icount > 2) + icount = 2; for (int ip = 0; ip < icount; ip++) { double d = cam_plane_eq.ValueAt(p[ip]); @@ -1684,6 +1698,8 @@ static int ClipArcToTextRect( { dimarc.ClosestPointTo(p[ip], &s[intcount]); intcount++; + if (intcount > 7) + intcount = 7; } } } @@ -2649,7 +2665,14 @@ bool ON_DimAngular::AdjustFromPoints( if (!ON_DimAngular::FindAngleVertex(lines, pickpoints, plane, center_point)) return false; - if (AdjustFromPoints(plane, center_point, pe1on, pe2on, dimline_pt)) + ON_3dPoint pd1 = pe1on; + ON_3dPoint pd2 = pe2on; + if (center_point.DistanceTo(pe1on) < ON_SQRT_EPSILON) + pd1 = pd1on; + if (center_point.DistanceTo(pe2on) < ON_SQRT_EPSILON) + pd2 = pd2on; + + if (AdjustFromPoints(plane, center_point, pd1, pd2, dimline_pt)) { m_ext_offset_1 = center_point.DistanceTo(pe1on); m_ext_offset_2 = center_point.DistanceTo(pe2on); @@ -3967,10 +3990,15 @@ bool ON_DimRadial::GetTextXform( double text_width = 0.0; double text_height = 0.0; + double line_height = 0.0; double text_gap = 0.0; + double landing_length = 0.0; const ON::TextOrientation text_orientation = dimstyle->DimRadialTextOrientation(); const ON_DimStyle::ContentAngleStyle text_alignment = dimstyle->DimRadialTextAngleStyle(); + const ON::TextHorizontalAlignment halign = dimstyle->LeaderTextHorizontalAlignment(); + //const ON::TextVerticalAlignment valign = dimstyle->LeaderTextVerticalAlignment(); + // Always move text to InLine if it's going to be horizontal and in the view plane const ON_DimStyle::TextLocation text_location = (ON::TextOrientation::InView == text_orientation) @@ -3991,7 +4019,9 @@ bool ON_DimRadial::GetTextXform( text_center = (cp[0] * dimscale + cp[2] * dimscale) / 2.0; text_width = (cp[1].x - cp[0].x) * dimscale; text_height = (cp[3].y - cp[0].y) * dimscale; + line_height = dimstyle->TextHeight() * dimscale; text_gap = dimstyle->TextGap() * dimscale; + landing_length = dimstyle->LeaderLandingLength() * dimscale; ON_2dPoint dimline_pt = DimlinePoint(); ON_2dPoint radius_pt = RadiusPoint(); @@ -4069,14 +4099,45 @@ bool ON_DimRadial::GetTextXform( // Text position adjustment ON_2dVector shift(0.0, 0.0); - if(ON_DimStyle::TextLocation::AboveDimLine == text_location) - shift.y = text_height / 2.0 + text_gap; + shift.y = -line_height / 2.0; + if (ON_DimStyle::TextLocation::AboveDimLine == text_location) + shift.y = text_gap + text_height; - shift.x = text_width / 2.0 + text_gap; - shift.x += dimstyle->LeaderLandingLength() * dimscale; + shift.x = text_gap; + shift.x += landing_length; + //shift.x += text_width / 2.0; if (-ON_SQRT_EPSILON > tail_dir.x) // text to left + { + switch (halign) + { + default: + case ON::TextHorizontalAlignment::Left: + shift.x += text_width; + break; + case ON::TextHorizontalAlignment::Right: + break; + case ON::TextHorizontalAlignment::Center: + shift.x += text_width / 2.0; + break; + } shift.y = -shift.y; + } + else + { + switch (halign) + { + default: + case ON::TextHorizontalAlignment::Left: + break; + case ON::TextHorizontalAlignment::Right: + shift.x += text_width; + break; + case ON::TextHorizontalAlignment::Center: + shift.x += text_width / 2.0; + break; + } + } if (dim_ydir * view_y < 0.0) shift.y = -shift.y; diff --git a/opennurbs_dimensionformat.cpp b/opennurbs_dimensionformat.cpp index a3cf1bbc..a84daf5c 100644 --- a/opennurbs_dimensionformat.cpp +++ b/opennurbs_dimensionformat.cpp @@ -235,7 +235,7 @@ bool ON_NumberFormatter::FormatNumber( if (0 != numerator) { if(bracket_fractions) - sFormat2.Format(L"[[/%d/%d]]", numerator, denominator); + sFormat2.Format(L"[[%d/%d]]", numerator, denominator); else sFormat2.Format(L"%d/%d", numerator, denominator); sFormat += sFormat2; @@ -313,14 +313,14 @@ bool ON_NumberFormatter::FormatNumber( if (wholeinches > 0 || include_feet) { if (bracket_fractions) - sInches.Format(L"%d [[/%d/%d]]\"", wholeinches, numerator, denominator); + sInches.Format(L"%d [[%d/%d]]\"", wholeinches, numerator, denominator); else sInches.Format(L"%d %d/%d\"", wholeinches, numerator, denominator); } else { if (bracket_fractions) - sInches.Format(L"[[/%d/%d]]\"", numerator, denominator); + sInches.Format(L"[[%d/%d]]\"", numerator, denominator); else sInches.Format(L"%d/%d\"", numerator, denominator); } diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index eebac150..33763a4d 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -99,17 +99,17 @@ unsigned int ONX_ErrorCounter::TotalCount() const unsigned int ONX_ErrorCounter::IncrementFailureCount() { - return ++m_failure_count; + return ++m_failure_count; // <- Good location for a debugger breakpoint. } unsigned int ONX_ErrorCounter::IncrementErrorCount() { - return ++m_error_count; + return ++m_error_count; // <- Good location for a debugger breakpoint. } unsigned int ONX_ErrorCounter::IncrementWarningCount() { - return ++m_warning_count; + return ++m_warning_count; // <- Good location for a debugger breakpoint. } void ONX_ErrorCounter::ClearLibraryErrors() @@ -127,8 +127,8 @@ unsigned int ONX_ErrorCounter::AddLibraryErrors() = bActive ? (m_opennurbs_library_error_count - count0) : 0U; - if (bActive) - m_error_count += count; + if (bActive && count > 0) + m_error_count += count; // <- Good location for a debugger breakpoint. return count; } @@ -147,8 +147,8 @@ unsigned int ONX_ErrorCounter::AddLibraryWarnings() = bActive ? (m_opennurbs_library_warning_count - count0) : 0U; - if (bActive) - m_warning_count += count; + if (bActive && count>0) + m_warning_count += count; // <- Good location for a debugger breakpoint. return count; } @@ -287,8 +287,14 @@ public: ONX_ModelComponentReferenceLink& operator=(const ONX_ModelComponentReferenceLink&) = default; public: + // m_mcr in this class is the "first" std::shared_ptr that manages + // the referenced ON_ModelComponent and is what insures ON_ModelComponent alive inside + // and ONX_Model for the duration of the model's lifetime. + // + // m_sn = ON_ModelComponent.RuntimeSerialNumber() so sn lookup can be safely used to find runtime pointer. + // When m_mcr.ModelComponent() is not nullptr, then m_sn = m_mcr.ModelComponent()->RuntimeSerialNumber(). ON_ModelComponentReference m_mcr; - ON__UINT64 m_sn = 0; + ON__UINT64 m_sn = 0; ONX_ModelComponentReferenceLink* m_next = nullptr; ONX_ModelComponentReferenceLink* m_prev = nullptr; }; @@ -575,6 +581,16 @@ ON_ModelComponentReference ONX_Model::ComponentFromId( : ON_ModelComponentReference::Empty; } +const ON_ModelComponentReference& ONX_Model::ComponentFromRuntimeSerialNumber( + ON__UINT64 runtime_serial_number +) const +{ + ONX_ModelComponentReferenceLink* link = Internal_ModelComponentLinkFromSerialNumber(runtime_serial_number); + return (nullptr != link) + ? link->m_mcr + : ON_ModelComponentReference::Empty; +} + ON_ModelComponentReference ONX_Model::ModelGeometryFromId( ON_UUID model_object_id ) const @@ -3445,34 +3461,37 @@ bool ONX_Model::Write( } // STEP 17: - write user tables (plug-in info, etc.) - if ( nullptr != m_model_user_string_list && m_model_user_string_list->UserStringCount() > 0 ) + if (archive.ArchiveContains3dmTable(ON_3dmArchiveTableType::user_table)) { - // Write the document user strings (key-value pairs) as - // a user table with plug-in id = - ON_UUID model_user_string_plugin_id = ON_CLASS_ID(ON_DocumentUserStringList); - if ( archive.BeginWrite3dmUserTable(model_user_string_plugin_id,false,0,0) ) + if (nullptr != m_model_user_string_list && m_model_user_string_list->UserStringCount() > 0) { - archive.WriteObject(m_model_user_string_list); - archive.EndWrite3dmUserTable(); - } - } - - // USER DATA TABLE - for( int i = 0; ok && i < m_userdata_table.Count(); i++ ) - { - const ONX_Model_UserData* model_ud = m_userdata_table[i]; - if (nullptr == model_ud) - continue; - if ( ON_UuidIsNotNil(model_ud->m_uuid) ) - { - if ( !archive.Write3dmAnonymousUserTableRecord( - model_ud->m_uuid, - model_ud->m_usertable_3dm_version, - model_ud->m_usertable_opennurbs_version, - model_ud->m_goo) - ) + // Write the document user strings (key-value pairs) as + // a user table with plug-in id = + ON_UUID model_user_string_plugin_id = ON_CLASS_ID(ON_DocumentUserStringList); + if (archive.BeginWrite3dmUserTable(model_user_string_plugin_id, false, 0, 0)) { + archive.WriteObject(m_model_user_string_list); + archive.EndWrite3dmUserTable(); + } + } + + // USER DATA TABLE + for (int i = 0; ok && i < m_userdata_table.Count(); i++) + { + const ONX_Model_UserData* model_ud = m_userdata_table[i]; + if (nullptr == model_ud) continue; + if (ON_UuidIsNotNil(model_ud->m_uuid)) + { + if (!archive.Write3dmAnonymousUserTableRecord( + model_ud->m_uuid, + model_ud->m_usertable_3dm_version, + model_ud->m_usertable_opennurbs_version, + model_ud->m_goo) + ) + { + continue; + } } } } @@ -3649,7 +3668,7 @@ void ONX_ModelComponentIterator::Internal_SetLink( m_current_component_sn = 0; m_next_component_sn = 0; m_prev_component_sn = 0; - m_current_component = ON_ModelComponentReference::Empty; + m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; m_model_content_version = 0; } else @@ -3657,7 +3676,7 @@ void ONX_ModelComponentIterator::Internal_SetLink( m_current_component_sn = link->m_sn; m_next_component_sn = (nullptr != link->m_next) ? link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != link->m_prev) ? link->m_prev->m_sn : 0; - m_current_component = link->m_mcr; + m_current_component_weak_ref = link->m_mcr; } } @@ -3667,35 +3686,58 @@ void ONX_ModelComponentIterator::Internal_SetLink( { } -void ONX_ModelComponentIterator::Internal_IncrementLink() const -{ -} - - -void ONX_ModelComponentIterator::Internal_DecrementLink() const -{ -} ON_ModelComponentReference ONX_ModelComponentIterator::FirstComponentReference() { - const ONX_Model::ONX_ModelComponentList* list = Internal_List(); - Internal_SetLink((nullptr != list) ? list->m_first_mcr_link : nullptr); - return m_current_component; + return ON_ModelComponentReference(FirstComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::LastComponentReference() { - const ONX_Model::ONX_ModelComponentList* list = Internal_List(); - Internal_SetLink((nullptr != list) ? list->m_last_mcr_link : nullptr); - return m_current_component; + return ON_ModelComponentReference(LastComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::CurrentComponentReference() const { - return m_current_component; + return ON_ModelComponentReference(CurrentComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::NextComponentReference() +{ + return ON_ModelComponentReference(NextComponentWeakReference()); +} + +ON_ModelComponentReference ONX_ModelComponentIterator::PreviousComponentReference() +{ + return ON_ModelComponentReference(PreviousComponentWeakReference()); +} + + + +ON_ModelComponentWeakReference ONX_ModelComponentIterator::FirstComponentWeakReference() +{ + const ONX_Model::ONX_ModelComponentList* list = Internal_List(); + Internal_SetLink((nullptr != list) ? list->m_first_mcr_link : nullptr); + return m_current_component_weak_ref; +} + +ON_ModelComponentWeakReference ONX_ModelComponentIterator::LastComponentWeakReference() +{ + const ONX_Model::ONX_ModelComponentList* list = Internal_List(); + Internal_SetLink((nullptr != list) ? list->m_last_mcr_link : nullptr); + return m_current_component_weak_ref; +} + +ON_ModelComponentWeakReference ONX_ModelComponentIterator::CurrentComponentWeakReference() const +{ + // unchanged ModelContentVersionNumber() means that m_link is safe to dreference. + // Otherwise use sn for safe reset. + if (m_model_content_version != m_model->ModelContentVersionNumber() ) + Internal_SetLink(m_model->Internal_ModelComponentLinkFromSerialNumber(m_current_component_sn)); + return m_current_component_weak_ref; +} + +ON_ModelComponentWeakReference ONX_ModelComponentIterator::NextComponentWeakReference() { if ( nullptr == m_list ) return FirstComponentReference(); @@ -3705,37 +3747,40 @@ ON_ModelComponentReference ONX_ModelComponentIterator::NextComponentReference() if (m_model_content_version == m_model->ModelContentVersionNumber() && nullptr != m_link ) { + // unchanged ModelContentVersionNumber() means that m_link is safe to dreference. m_link = m_link->m_next; if (nullptr == m_link) { m_prev_component_sn = m_current_component_sn; m_current_component_sn = 0; m_next_component_sn = 0; - m_current_component = ON_ModelComponentReference::Empty; + m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } else { m_current_component_sn = m_link->m_sn; m_next_component_sn = (nullptr != m_link->m_next) ? m_link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != m_link->m_prev) ? m_link->m_prev->m_sn : 0; - m_current_component = m_link->m_mcr; + m_current_component_weak_ref = m_link->m_mcr; } } else if ( 0 != m_next_component_sn ) { + // Otherwise m_link is not safe to dereference. + // Use slower serial number lookup. Internal_SetLink(m_model->Internal_ModelComponentLinkFromSerialNumber(m_next_component_sn)); } else { m_link = nullptr; m_current_component_sn = 0; - m_current_component = ON_ModelComponentReference::Empty; + m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } - return m_current_component; + return m_current_component_weak_ref; } -ON_ModelComponentReference ONX_ModelComponentIterator::PreviousComponentReference() +ON_ModelComponentWeakReference ONX_ModelComponentIterator::PreviousComponentWeakReference() { if ( nullptr == m_list ) return LastComponentReference(); @@ -3751,14 +3796,14 @@ ON_ModelComponentReference ONX_ModelComponentIterator::PreviousComponentReferenc m_next_component_sn = m_current_component_sn; m_current_component_sn = 0; m_prev_component_sn = 0; - m_current_component = ON_ModelComponentReference::Empty; + m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } else { m_current_component_sn = m_link->m_sn; m_next_component_sn = (nullptr != m_link->m_next) ? m_link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != m_link->m_prev) ? m_link->m_prev->m_sn : 0; - m_current_component = m_link->m_mcr; + m_current_component_weak_ref = m_link->m_mcr; } } else if ( 0 != m_prev_component_sn ) @@ -3769,10 +3814,10 @@ ON_ModelComponentReference ONX_ModelComponentIterator::PreviousComponentReferenc { m_link = nullptr; m_current_component_sn = 0; - m_current_component = ON_ModelComponentReference::Empty; + m_current_component_weak_ref = ON_ModelComponentReference::Empty; } - return m_current_component; + return m_current_component_weak_ref; } const ON_ModelComponent* ONX_ModelComponentIterator::FirstComponent() diff --git a/opennurbs_extensions.h b/opennurbs_extensions.h index ded85638..7ac1695b 100644 --- a/opennurbs_extensions.h +++ b/opennurbs_extensions.h @@ -1020,6 +1020,31 @@ public: const ON_NameHash& component_model_name_hash ) const; + /* + Parameters: + runtime_serial_number - [in] + Value of ON_ModelComponent::RuntimeSerialNumber() to search for. + Returns: + If there is a model component with the specified runtime serial number, + then a reference to that component is returned. + Otherwise, ON_ModelComponentReference::Empty is returned. + Remarks: + ONX_Model::ComponentFromRuntimeSerialNumber() used to get a reference rather than a copy of the model's + primary ON_ModelComponentReference. This is the function that must be used if a caller is going to + use exclusive access funcitons like + + ON_ModelComponent* ON_ModelComponentReference::ExclusiveModelComponent() + ON_3dmObjectAttributes* ON_ModelGeometryComponent::ExclusiveAttributes() + ON_Geometry* ON_ModelGeometryComponent::ExclusiveGeometry() + + to modify content that is in the ONX_Model. The exclusive access functions + will only return non-nullptr values when there are no external references to + the model component. + */ + const ON_ModelComponentReference& ComponentFromRuntimeSerialNumber( + ON__UINT64 runtime_serial_number + ) const; + /* Description: Get an image from its model index. @@ -1557,6 +1582,14 @@ public: ON_ModelComponentReference NextComponentReference(); ON_ModelComponentReference PreviousComponentReference(); + ON_ModelComponentWeakReference FirstComponentWeakReference(); + ON_ModelComponentWeakReference LastComponentWeakReference(); + ON_ModelComponentWeakReference NextComponentWeakReference(); + ON_ModelComponentWeakReference PreviousComponentWeakReference(); + ON_ModelComponentWeakReference CurrentComponentWeakReference() const; + + // Use these with caution unless it is clear you are the only thread + // with references to the model and the iterator. const ON_ModelComponent* FirstComponent(); const ON_ModelComponent* LastComponent(); const ON_ModelComponent* CurrentComponent() const; @@ -1575,8 +1608,6 @@ private: const class ONX_Model::ONX_ModelComponentList* Internal_List() const; void Internal_SetLink(const class ONX_ModelComponentReferenceLink* link) const; void Internal_SetLink(ON__UINT64 model_component_sn) const; - void Internal_IncrementLink() const; - void Internal_DecrementLink() const; ON_ModelComponent::Type m_component_type = ON_ModelComponent::Type::Unset; const class ONX_Model* m_model = nullptr; @@ -1586,22 +1617,16 @@ private: mutable ON__UINT64 m_current_component_sn = 0; mutable ON__UINT64 m_next_component_sn = 0; mutable ON__UINT64 m_prev_component_sn = 0; - mutable ON_ModelComponentReference m_current_component = ON_ModelComponentReference::Empty; -}; -/* -Description: - Tests a string to see if it is valid as a name for a layer, - object, material, linetype, instance definition, etc. -Parameters: - name - [in] string to test -Returns: - True if the string is a valid name. -*/ -ON_DECL -bool ONX_IsValidName( - const wchar_t* name - ); + // The current component is a weak ref so that a stand alone iterator cannot + // keep the current element alive since iterations often involve deletion. + // The iterators next/prev will still work as expected when the current element + // is deleted. In particular, an iterator can be used to efficiently delete + // portions of a model and have the deletion occur when many people + // expect it to occur and not at a later time. This makes debugging + // invalid deletions much easier. + mutable ON_ModelComponentWeakReference m_current_component_weak_ref; +}; class ON_CLASS ONX_ModelTest { diff --git a/opennurbs_font.cpp b/opennurbs_font.cpp index ab51db54..1ea2fa21 100644 --- a/opennurbs_font.cpp +++ b/opennurbs_font.cpp @@ -1103,74 +1103,6 @@ const ON_Font* ON_ManagedFonts::Internal_AddManagedFont( // BEGIN list of platform ON_Fonts // -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - -//static NSString* Internal_FontCategory (NSFont* font) -//{ -// // determine file path for NSFont -// CTFontRef fontRef = (__bridge CTFontRef) font; -// CFURLRef fontURLRef = (CFURLRef) CTFontCopyAttribute (fontRef, kCTFontURLAttribute); -// NSURL* fontURL = (NSURL*) CFBridgingRelease (fontURLRef); -// NSString* fontPath = fontURL.path; -// if ([fontPath hasPrefix: @"/System/"]) -// return @"System"; -// if ([fontPath hasPrefix: @"/Library/Fonts/Microsoft/"]) -// return @"Microsoft"; -// if ([fontPath hasPrefix: @"/Library/Fonts/"]) -// return @"Library"; -// return @"User"; -//} - - -//static bool Internal_IsSystemFont (NSFont* font) -//{ -// // determine file path for NSFont -// CTFontRef fontRef = (__bridge CTFontRef) font; -// CFURLRef fontURLRef = (CFURLRef) CTFontCopyAttribute (fontRef, kCTFontURLAttribute); -// NSURL* fontURL = (NSURL*) CFBridgingRelease (fontURLRef); -// NSString* fontPath = fontURL.path; -// return [fontPath hasPrefix: @"/System/"] || [fontPath hasPrefix: @"/Library/"]; -//} - - -static void Internal_GetAppleInstalledFonts( - ON_SimpleArray& platform_font_list -) -{ - const bool bAnnotationFont = true; - const double pointSize = ON_Font::Constants::AnnotationFontApplePointSize; - - - for (NSString* fontManagerName in[NSFontManager.sharedFontManager availableFonts]) - { - const bool bIsInternalSystemFont = [fontManagerName hasPrefix : @"."]; - if (bIsInternalSystemFont) - { - continue; // skip internal system fonts - } - - const ON_String utf8fontManagerName(fontManagerName.UTF8String); - - NSFont* apple_font = [NSFont fontWithName : fontManagerName size : pointSize]; - if (apple_font == nil) - { - continue; // damaged font - } - - const ON_wString ON_Font_apple_font_name(apple_font.fontName.UTF8String); - - //NSString* fontCategory = Internal_FontCategory(apple_font); - - ON_Font* platform_font = new ON_Font(); - if (false == platform_font->SetFromAppleFont(apple_font, bAnnotationFont)) - { - delete platform_font; - continue; - } - platform_font_list.Append(platform_font); - } -} -#endif #if defined(ON_RUNTIME_WIN) @@ -3263,7 +3195,7 @@ const ON_Font* ON_FontList::FromFamilyName( const wchar_t* prefered_face_name ) const { - return FromFamilyName(family_name, prefered_face_name, ON_Font::Weight::Normal, ON_Font::Stretch::Condensed, ON_Font::Style::Upright); + return FromFamilyName(family_name, prefered_face_name, ON_Font::Weight::Normal, ON_Font::Stretch::Medium, ON_Font::Style::Upright); } const ON_Font* ON_FontList::FromFamilyName( @@ -3466,7 +3398,10 @@ const ON_Font* ON_FontList::Internal_FromNames( key.m_font_stretch = prefered_stretch; key.m_font_style = prefered_style; - if (key.m_loc_family_name.IsEmpty() || key.m_loc_face_name.IsEmpty()) + + const bool bKeyHasFamilyAndFace = key.m_loc_family_name.IsNotEmpty() && key.m_loc_face_name.IsNotEmpty(); + + if (false == bKeyHasFamilyAndFace) bRequireFaceMatch = false; int pass_count = 0; @@ -3606,12 +3541,34 @@ const ON_Font* ON_FontList::Internal_FromNames( } if (bRequireStyleMatch && prefered_style != candidate->FontStyle()) continue; - if (bRequireFaceMatch && candidate->FamilyName().IsNotEmpty() && false == ON_Font::EqualFontFamilyAndFace(&key, candidate)) + + const bool bCandidateFamilyAndFaceMatch + = bKeyHasFamilyAndFace + && (ON_Font::EqualFontFamilyAndFace(&key, candidate) || 0 == ON_FontList::CompareEnglishFamilyAndFaceName(&pkey,&candidate)); + + if (bRequireFaceMatch && candidate->FamilyName().IsNotEmpty() && false == bCandidateFamilyAndFaceMatch) continue; + + const bool bFontFamilyAndFaceMatch + = bKeyHasFamilyAndFace + && (nullptr != font) + && (ON_Font::EqualFontFamilyAndFace(&key, font) || 0 == ON_FontList::CompareEnglishFamilyAndFaceName(&pkey,&candidate)); + + if (bFontFamilyAndFaceMatch && false == bCandidateFamilyAndFaceMatch) + continue; + candidate_dev = ON_Font::WeightStretchStyleDeviation(prefered_weight, prefered_stretch, prefered_style, candidate); if (0 == candidate_dev) - return candidate; - if (nullptr == font || candidate_dev < font_dev) + { + if ( bCandidateFamilyAndFaceMatch || false == bKeyHasFamilyAndFace) + return candidate; + } + + if ( + nullptr == font + || (bCandidateFamilyAndFaceMatch && false == bFontFamilyAndFaceMatch) + || candidate_dev < font_dev + ) { font = candidate; font_dev = candidate_dev; @@ -3870,7 +3827,7 @@ const ON_Font* ON_Font::ManagedFamilyMemberWithRichTextProperties( bStrikethrough = bStrikethrough ? true : false; const ON_Font::Weight desired_weight - = (bBold != IsBold()) + = (bBold != IsBoldInQuartet()) ? (bBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal) : FontWeight(); @@ -4552,19 +4509,6 @@ const ON_Font* ON_Font::GetManagedFontFromPostScriptName( return font.ManagedFont(); } -#if defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) -const ON_Font* ON_Font::GetManagedFontFromAppleFont( - NSFont* apple_font, - bool bAnnotationFont -) -{ - ON_Font font_characteristics; - if ( false == font_characteristics.SetFromAppleFont(apple_font,bAnnotationFont) ) - return nullptr; - return font_characteristics.ManagedFont(); -} -#endif - int ON_Font::WindowsLogfontWeightFromWeight( ON_Font::Weight font_weight ) @@ -5813,319 +5757,6 @@ bool ON_Font::SetFontCharacteristics( return true; } -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) -bool ON_Font::SetFromAppleFont( NSFont* apple_font, bool bAnnotationFont ) -{ - - if ( false == ON_FONT_MODIFICATION_PERMITTED ) - return false; - - *this = ON_Font::Unset; - - const ON_wString postscript_name = ON_Font::PostScriptNameFromAppleFont(apple_font); - const ON_wString family_name = ON_Font::FamilyNameFromAppleFont(apple_font); - const ON_wString face_name = ON_Font::FaceNameFromAppleFont(apple_font); - - // Set Windows LOGFONT.lfFaceName to something not empty there is some hope this - // font might work in Rhino 5 for Windows too. - // https://mcneel.myjetbrains.com/youtrack/issue/RH-37074 - const ON_wString windows_logfont_name = family_name; - - const bool rc = postscript_name.IsNotEmpty() || family_name.IsNotEmpty(); - if (rc) - { - m_loc_postscript_name = postscript_name; - m_en_postscript_name = postscript_name; - m_loc_family_name = family_name; - m_en_family_name = family_name; - m_loc_face_name = face_name; - m_en_face_name = face_name; - m_loc_windows_logfont_name = windows_logfont_name; - m_en_windows_logfont_name = windows_logfont_name; - - // Can get font metrics from NSFontDescriptor - // https://developer.apple.com/library/content/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html - // defaultLineHeight(for theFont: NSFont) - // fd.xHeight, fd.ascender, fd.descender, fd.capHeight, fd.defaultLineHeightForFont - - // Set weight - used if this font needs sustution on another computer - // https://mcneel.myjetbrains.com/youtrack/issue/RH-37075 - NSFontDescriptor* fd = apple_font.fontDescriptor; - NSDictionary* traits = [fd objectForKey: NSFontTraitsAttribute]; - NSNumber* weightValue = [traits objectForKey: NSFontWeightTrait]; - if (weightValue) - { - const double apple_font_weight_trait = weightValue.doubleValue; - SetAppleFontWeightTrait(apple_font_weight_trait); - } - else if ( 0 != (fd.symbolicTraits & NSFontBoldTrait) ) - SetFontWeight(ON_Font::Weight::Bold); - else - SetFontWeight(ON_Font::Weight::Normal); - - // Set style - used if this font needs sustution on another computer - if ( 0 != (fd.symbolicTraits & NSFontItalicTrait) ) - m_font_style = ON_Font::Style::Italic; - else - m_font_style = ON_Font::Style::Upright; - - if ( 0 != (fd.symbolicTraits & NSFontExpandedTrait) ) - m_font_stretch = ON_Font::Stretch::Expanded; - else if ( 0 != (fd.symbolicTraits & NSFontCondensedTrait) ) - m_font_stretch = ON_Font::Stretch::Condensed; - else - m_font_stretch = ON_Font::Stretch::Medium; - - // Saving point size added January 2018. - const double point_size = (double)apple_font.pointSize; - m_point_size - = (false == bAnnotationFont && ON_Font::IsValidPointSize(point_size) ) - ? point_size - : 0.0; // indicates annotation size will be used - - m_logfont_charset = ON_Font::WindowsConstants::logfont_default_charset; - - // do this again because some of the above calls can modify description - m_loc_postscript_name = postscript_name; - m_en_postscript_name = m_loc_postscript_name; - m_loc_family_name = family_name; - m_en_family_name = m_loc_family_name; - m_loc_face_name = face_name; - m_en_face_name = m_loc_family_name; - m_loc_windows_logfont_name = windows_logfont_name; - m_en_windows_logfont_name = windows_logfont_name; - - SetFontOrigin(ON_Font::Origin::AppleFont); - } - - return rc; -} - -const ON_wString ON_Font::PostScriptNameFromAppleFont( - NSFont* apple_font -) -{ - if (nullptr == apple_font) - return ON_wString::EmptyString; - const ON_String utf8_postscript_name(apple_font.fontName.UTF8String); - ON_wString postscript_name(utf8_postscript_name); - postscript_name.TrimLeftAndRight(); - return postscript_name; -} - -const ON_wString ON_Font::FamilyNameFromAppleFont( - NSFont* apple_font - ) -{ - if (nullptr == apple_font) - return ON_wString::EmptyString; - const ON_String utf8_family_name(apple_font.familyName.UTF8String); - ON_wString family_name(utf8_family_name); - family_name.TrimLeftAndRight(); - return family_name; -} - -const ON_wString ON_Font::FaceNameFromAppleFont( - NSFont* apple_font - ) -{ - for(;;) - { - if (nullptr == apple_font) - break; - - const ON_String utf8_family_and_face_name(apple_font.displayName.UTF8String); - ON_wString family_and_face_name(utf8_family_and_face_name); - family_and_face_name.TrimLeftAndRight(); - if ( family_and_face_name.IsEmpty() ) - break; - const wchar_t* family_and_face_str = static_cast(family_and_face_name); - if (nullptr == family_and_face_str) - break; - - const ON_wString family_name = ON_Font::FamilyNameFromAppleFont(apple_font); - if ( family_name.IsEmpty() ) - break; - const wchar_t* family_str = static_cast(family_name); - if (nullptr == family_str) - break; - - // It is not always the case that the beginning of - // apple_font.displayName.UTF8String exactly matches - // apple_font.familyName.UTF8String. We need to ignore - // spaces and hyphens. - for (;;) - { - wchar_t a = *family_str; - while (ON_wString::Space == a || ON_wString::HyphenMinus == a) - a = *(++family_str); - - wchar_t b = *family_and_face_str; - while (ON_wString::Space == b || ON_wString::HyphenMinus == b) - b = *(++family_and_face_str); - - if (0 == a || 0 == b) - break; - a = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,a); - b = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::MinimumOrdinal,b); - if (a != b) - break; - family_str++; - family_and_face_str++; - } - ON_wString face_name(family_and_face_str); - face_name.TrimLeftAndRight(); - if ( face_name.IsEmpty()) - break; - return face_name; - } - - return ON_wString::EmptyString; -} - - -NSFont* ON_Font::AppleFont() const -{ - // Using PointSize() added January 2018. - const double annotation_font_point_size = (double)ON_Font::Constants::AnnotationFontApplePointSize; - double pointSize - = ON_Font::IsValidPointSize(m_point_size) - ? m_point_size - : annotation_font_point_size; - - return AppleFont(pointSize); -} - -NSFont* ON_Font::AppleFont(double pointSize) const -{ - const double annotation_font_point_size = (double)ON_Font::Constants::AnnotationFontApplePointSize; - NSFont* userFont = nullptr; - - // TODO - Use ON_Font::InstalledFontFromNames(...) to search installed fonts instead of the following - - for (int pass = 0; pass < 2; pass++) - { - if ( 0 != pass) - pointSize = annotation_font_point_size; - - // TODO - replace the following with ON_Font::InstalledFontFromNames(....) - - for(int ps_loc = 0; ps_loc < 2; ps_loc++) - { - // NOTE WELL: - // The post script name is NOT unique for simulated fonts - // (Bahnschrift and other OpenType variable fonts being one common source of simulated fonts.) - const ON_wString postscript_name - = (0 == ps_loc) - ? m_loc_postscript_name - : m_en_postscript_name; - - if (ps_loc > 0 && postscript_name == m_loc_postscript_name) - break; - - if (postscript_name.IsNotEmpty()) - { - // m_apple_font name was a Mac OS font name (NSFont.fontName = Mac OS FontBook "PostScript" name) on some Apple platform device. - // If the current computer has the same font, this will return the - // highest fidelity match. - const ON_String UTF8_postscript_name(postscript_name); - NSString* fontPostscriptName = [NSString stringWithUTF8String : UTF8_postscript_name]; - userFont = [NSFont fontWithName : fontPostscriptName size : pointSize]; - if (userFont) - break; - } - } - - // Try getting a NSFont by using NSFontManager - NSFontTraitMask traitsStyleStretch = 0; - if (IsItalic()) - traitsStyleStretch |= NSItalicFontMask; - if (FontStretch() <= ON_Font::Stretch::Condensed) - traitsStyleStretch |= NSCondensedFontMask; - if (FontStretch() >= ON_Font::Stretch::Expanded) - traitsStyleStretch |= NSExpandedFontMask; - - ON_String family_name = FamilyName(); // convert to UTF8 - NSString* fontFamilyName = [NSString stringWithUTF8String : family_name]; // font family name - - // https://developer.apple.com/documentation/appkit/nsfontmanager/1462332-fontwithfamily - // weight - // A hint for the weight desired, on a scale of 0 to 15: - // a value of 5 indicates a normal or book weight, - // and 9 or more a bold or heavier weight. - // The weight is ignored if fontTraitMask includes NSBoldFontMask. - if (IsBold() || IsNormalWeight()) - { - NSFontTraitMask traitsStyleStretchWeight - = IsBold() - ? (traitsStyleStretch | NSBoldFontMask) - : (traitsStyleStretch | NSUnboldFontMask); - userFont = [[NSFontManager sharedFontManager] fontWithFamily:fontFamilyName traits : traitsStyleStretchWeight weight : 5 size : pointSize]; - if (userFont) - break; - } - - int weightFromFontWeight; - switch (FontWeight()) - { - case ON_Font::Weight::Thin: - weightFromFontWeight = 2; - break; - case ON_Font::Weight::Ultralight: - weightFromFontWeight = 3; - break; - case ON_Font::Weight::Light: - weightFromFontWeight = 4; - break; - case ON_Font::Weight::Normal: - weightFromFontWeight = 5; - break; - case ON_Font::Weight::Medium: - weightFromFontWeight = 6; - break; - case ON_Font::Weight::Semibold: - weightFromFontWeight = 7; - break; - case ON_Font::Weight::Bold: - weightFromFontWeight = 9; - break; - case ON_Font::Weight::Ultrabold: - weightFromFontWeight = 12; - break; - case ON_Font::Weight::Heavy: - weightFromFontWeight = 15; - break; - default: - weightFromFontWeight = 5; - break; - } - - userFont = [[NSFontManager sharedFontManager] fontWithFamily:fontFamilyName traits : traitsStyleStretch weight : weightFromFontWeight size : pointSize]; - if (userFont) - break; - - // Try using just FontFaceName() - userFont = [NSFont fontWithName : fontFamilyName size : pointSize]; - if (userFont) - break; - - // Cannot find an equivalent font. Just use a system font. - userFont = [NSFont userFontOfSize : pointSize]; - if (userFont) - break; - - userFont = [NSFont systemFontOfSize : pointSize]; - if (userFont) - break; - - if (annotation_font_point_size == pointSize) - break; // 2nd pass will not help - } - - return userFont; -} - -#endif bool ON_Font::SetFromAppleFontName( @@ -6148,25 +5779,21 @@ bool ON_Font::SetFromAppleFontName( apple_font_name = static_cast(local_apple_font_name); const bool bAnnotationFont - = ON_Font::IsValidPointSize(point_size) + = ON_Font::IsValidPointSize(point_size) && point_size < ON_Font::AnnotationFontCellHeight ? false : true; bool rc = false; -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) // If the current computer has the same Apple font, this will return the highest fidelity match. - const double pointSize - = bAnnotationFont - ? (double)ON_Font::Constants::AnnotationFontApplePointSize - : point_size; - const ON_String UTF8_apple_font_name(apple_font_name); - NSString* fullFontName = [NSString stringWithUTF8String : UTF8_apple_font_name]; - NSFont* userFont = [NSFont fontWithName : fullFontName size : pointSize]; - if (userFont) + CTFontRef appleFont = ON_Font::AppleCTFont(apple_font_name,point_size); + if (nullptr != appleFont) { // In some cases, the NSfont.fontName is different from apple_font_name // For example, when apple_font_name = "Arial", NSfont.fontName = "ArialMT". - if (SetFromAppleFont(userFont, bAnnotationFont)) + const bool b = SetFromAppleCTFont(appleFont, bAnnotationFont); + CFRelease(appleFont); + if(b) return true; } #endif @@ -6394,6 +6021,11 @@ const ON_wString Internal_RichTextCleaner return clean_name; } +const ON_wString ON_Font::RichTextFontName() const +{ + return ON_Font::RichTextFontName(this, false); +} + const ON_wString ON_Font::RichTextFontName( const ON_Font* font, bool bDefaultIfEmpty @@ -6522,7 +6154,6 @@ public: const wchar_t* m_family_name = nullptr; }; - static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName( const InternalHashToName* key ) @@ -6602,7 +6233,8 @@ static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName( // NOTE WELL: // Orach Technic 2L fonts are NOT a double stroke fonts. // The figure outlines are simple closed perimenters - // that contain postivie areas. + // that contain postive areas. + // The behave poorly in simulated bold face. }; static InternalHashToName perimeter_name_map[] = @@ -6678,6 +6310,83 @@ static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName( return ON_OutlineFigure::Type::Unknown; } +static bool Internal_IsEngravingFont( + const InternalHashToName* key +) +{ + + // The structure of the font is a "perimeter", but the intended use + // is for engraving. The OrachTech*2* fonts behave poorly when used + // as perimeter fonts. + static InternalHashToName double_line_name_map[] = + { + // Orach Technic 2L + InternalHashToName(L"OrachTech2Lotf"), // Family + InternalHashToName(L"OrachTech2Lotf"), // PostScript + InternalHashToName(L"OrachTechDemo2Lotf"), // Family + InternalHashToName(L"OrachTechDemo2Lotf"), // PostScript + InternalHashToName(L"OrachTech2Lttf"), // Family + InternalHashToName(L"OrachTech2Lttf"), // PostScript + InternalHashToName(L"OrachTechDemo2Lttf"), // Family + InternalHashToName(L"OrachTechDemo2Lttf"), // PostScript + }; + + static size_t double_line_name_map_count = 0; + + const InternalHashToName* e; + const size_t sizeof_e = sizeof(*e); + + if (0 == double_line_name_map_count) + { + double_line_name_map_count = InternalHashToName::SortAndCullByHash(double_line_name_map, (sizeof(double_line_name_map) / sizeof_e)); + } + + e = (const InternalHashToName*)bsearch(key, double_line_name_map, double_line_name_map_count, sizeof_e, InternalHashToName::CompareHash); + if (nullptr != e) + return true; + + ON_OutlineFigure::Type outline_type = Internal_FigureTypeFromHashedFontName(key); + if (ON_OutlineFigure::Type::SingleStroke == outline_type) + return true; + if (ON_OutlineFigure::Type::DoubleStroke == outline_type) + return true; + + return false; +} + + +const wchar_t* ON_OutlineFigure::OrientationToWideString( + ON_OutlineFigure::Orientation orientation +) +{ + const wchar_t* s; + + switch (orientation) + { + case ON_OutlineFigure::Orientation::Unset: + s = L"Unset"; + break; + case ON_OutlineFigure::Orientation::CounterClockwise: + s = L"CounterClockwise"; + break; + case ON_OutlineFigure::Orientation::Clockwise: + s = L"Clockwise"; + break; + case ON_OutlineFigure::Orientation::NotOriented: + s = L"NotOriented"; + break; + case ON_OutlineFigure::Orientation::Error: + s = L"Error"; + break; + default: + s = L""; + break; + } + + return s; +} + + ON_OutlineFigure::Type ON_OutlineFigure::FigureTypeFromFontName( const wchar_t* font_name ) @@ -6759,6 +6468,52 @@ bool ON_Font::IsSingleStrokeOrDoubleStrokeFont() const ); } +bool ON_Font::IsEngravingFont() const +{ + const ON_wString names[] = + { + FamilyName(), + FamilyName(ON_Font::NameLocale::English), + PostScriptName(), + PostScriptName(ON_Font::NameLocale::English) + }; + + InternalHashToName key[sizeof(names)/sizeof(names[0])]; + + const int name_count = (int)(sizeof(names) / sizeof(names[0])); + int key_count = 0; + for (int i = 0; i < name_count; i++) + { + const ON_wString& name = names[i]; + if (name.IsEmpty()) + continue; + + // computing the hash is much more expensive than checking for duplicate names (which is common) + bool bSkipName = name.IsEmpty(); + for (int j = 0; false == bSkipName && j < i; j++) + bSkipName = (name == names[j]); + if (bSkipName) + continue; + + // compute name hash + key[key_count] = InternalHashToName(name, nullptr); + + // searching for a duplicate hash is more expensive than checking for duplicate hash + // (which is common because of space and hyphen differences between family and postscript names) + for (int j = 0; false == bSkipName && j < key_count; j++) + bSkipName = (key[key_count].m_dirty_name_hash == key[i].m_dirty_name_hash); + if (bSkipName) + continue; + + // search for matching name hash is lists of known single stroke and double stroke fonts + bool bIsEngravingFont = Internal_IsEngravingFont(&key[key_count]); + key_count++; + if (bIsEngravingFont) + return true; + } + return false; +} + const ON_wString ON_Font::FamilyNameFromDirtyName( const wchar_t* dirty_name @@ -7370,16 +7125,17 @@ bool ON_Font::SetFromPostScriptName( #if defined(ON_RUNTIME_WIN) // TODO: Get Windows IDWriteFont with matching post_script_name -#elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) // If the current computer has the same font, this will return the // highest fidelity match. - const ON_String UTF8_postscript_font_name(postscript_font_name); - NSString* fullFontName = [NSString stringWithUTF8String : UTF8_postscript_font_name]; - double pointSize = (double)ON_Font::Constants::AnnotationFontApplePointSize; - NSFont* apple_font = [NSFont fontWithName : fullFontName size : pointSize]; + // NOTE WELL: The NSFont API fails for some valid PostScript names. + // For example, the some of the 14 or so faces Helvetica Neue + // are not round tripped using the NSFont by name creation and + // are round tripped by using the CTFont API. + CTFontRef apple_font = ON_Font::AppleCTFont(postscript_font_name,0.0); if (nullptr != apple_font) { - return SetFromAppleFont(apple_font,true); + return SetFromAppleCTFont(apple_font,true); } #endif @@ -7769,21 +7525,26 @@ void ON_Font::Dump(ON_TextLog& dump) const } #endif -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) { - NSFont* apple_font = AppleFont(); - dump.Print(L"Apple font:\n"); + CTFontRef apple_font = AppleCTFont(); + dump.Print(L"Apple Core Text font:\n"); dump.PushIndent(); - ON_Font::DumpNSFont(apple_font, dump); + ON_Font::DumpCTFont(apple_font, dump); dump.PopIndent(); } #endif +#if defined(OPENNURBS_FREETYPE_SUPPORT) +// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. +// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). if (runtime_sn >= 1) { // Free Type font details DumpFreeType(dump); } +#endif + } dump.PopIndent(); } @@ -7823,33 +7584,6 @@ void ON_FontMetrics::Dump(class ON_TextLog& text_log) const text_log.Print("Underscore position = %d\n", m_underscore_position); } - -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) -void ON_Font::DumpNSFont( - NSFont* apple_font, - ON_TextLog& text_log -) -{ - if (nullptr == apple_font) - { - text_log.Print("NSFont = nullptr\n"); - return; - } - - text_log.Print("NSFont\n"); - text_log.PushIndent(); - const ON_String utf8fontName(apple_font.fontName.UTF8String); - text_log.Print("NSFont.fontName: \"%s\"\n",static_cast(utf8fontName)); - const ON_String utf8displayName(apple_font.displayName.UTF8String); - text_log.Print("NSFont.displayName: \"%s\"\n",static_cast(utf8displayName)); - const ON_String utf8familyName(apple_font.familyName.UTF8String); - text_log.Print("NSFont.familyName: \"%s\"\n",static_cast(utf8familyName)); - const double point_size = (double)apple_font.pointSize; - text_log.Print("NSFont.pointSize: \"%g\"\n",point_size); - text_log.PopIndent(); -} -#endif - #if defined(ON_OS_WINDOWS_GDI) #define INTERNAL_CASE_STRING(c) case c: s = #c; break @@ -8870,7 +8604,7 @@ bool ON_Font::ReadV5( if ( 0 != bItalic ) font_style = ON_Font::Style::Italic; - double obsolete_linefeed_ratio = 1.6; + double obsolete_linefeed_ratio = ON_FontMetrics::DefaultLineFeedRatio; if (!file.ReadDouble(&obsolete_linefeed_ratio)) break; @@ -11145,8 +10879,8 @@ const ON_FontGlyph* ON_Font::Internal_ManagedCodePointGlyph( // Call external function to get glyph details ON_TextBox font_unit_glyph_box = ON_TextBox::Unset; - ON__UINT_PTR font_glyph_id = ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits(this, unicode_code_point, font_unit_glyph_box); - if (0 != font_glyph_id) + const unsigned int font_glyph_index = ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits(this, unicode_code_point, font_unit_glyph_box); + if (0 != font_glyph_index) { if (font_unit_glyph_box.IsSet()) { @@ -11158,7 +10892,7 @@ const ON_FontGlyph* ON_Font::Internal_ManagedCodePointGlyph( normalized_glyph_box = ON_TextBox::Scale(font_unit_glyph_box, normalize_scale); glyph.m_normalized_glyph_bbox = normalized_glyph_box; } - glyph.Internal_SetFontGlyphId(font_glyph_id); + glyph.Internal_SetFontGlyphIndex(font_glyph_index); bFindSubstitutes = false; } @@ -11317,31 +11051,35 @@ void ON_Font::SetCustomMeasurementFunctions( ON_Font::Internal_CustomGetFontMetricsFunc = metricsFunc; } -ON__UINT_PTR ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits( +unsigned int ON_ManagedFonts::GetGlyphMetricsInFontDesignUnits( const class ON_Font* font, ON__UINT32 unicode_code_point, class ON_TextBox& glyph_metrics_in_font_design_units ) { - ON__UINT_PTR glyph_id = 0; + unsigned int glyph_index = 0; const ON_FontGlyph g(font, unicode_code_point); if (nullptr != ON_Font::Internal_CustomGetGlyphMetricsFunc) { - glyph_id = ON_Font::Internal_CustomGetGlyphMetricsFunc(&g, glyph_metrics_in_font_design_units); - if (0 != glyph_id) - return glyph_id; + glyph_index = ON_Font::Internal_CustomGetGlyphMetricsFunc(&g, glyph_metrics_in_font_design_units); + if (0 != glyph_index) + return glyph_index; } #if defined(ON_OS_WINDOWS_GDI) - glyph_id = ON_WindowsDWriteGetGlyphMetrics(&g, glyph_metrics_in_font_design_units); + glyph_index = ON_WindowsDWriteGetGlyphMetrics(&g, glyph_metrics_in_font_design_units); +#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + glyph_index = ON_AppleFontGetGlyphMetrics(&g, glyph_metrics_in_font_design_units); #elif defined(OPENNURBS_FREETYPE_SUPPORT) - glyph_id = ON_FreeTypeGetGlyphMetrics(&g, glyph_metrics_in_font_design_units); + // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. + // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). + glyph_index = ON_FreeTypeGetGlyphMetrics(&g, glyph_metrics_in_font_design_units); #endif - if (0 != glyph_id) - return glyph_id; + if (0 != glyph_index) + return glyph_index; glyph_metrics_in_font_design_units = ON_TextBox::Unset; return 0; @@ -11380,18 +11118,11 @@ bool ON_ManagedFonts::GetFontMetricsInFontDesignUnits( #if defined(ON_OS_WINDOWS_GDI) ON_WindowsDWriteGetFontMetrics(font, fm); - if (fm.AscentDescentAndUPMAreValid()) - break; -#elif defined(ON_RUNTIME_APPLE) && defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - ON_AppleNSFontGetFontMetrics(font, fm); - if (fm.AscentDescentAndUPMAreValid()) - break; -#endif - -#if defined(OPENNURBS_FREETYPE_SUPPORT) - // Last try - freetype is the least desirable option because - // it has hacks to get capital height and bugs in support - // for .ttc files and variable type fonts like Bahnschrift. +#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + ON_AppleFontGetFontMetrics(font, fm); +#elif defined(OPENNURBS_FREETYPE_SUPPORT) + // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. + // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). ON_FreeTypeGetFontMetrics(font, fm); #endif @@ -11439,9 +11170,9 @@ const ON_FontList& ON_ManagedFonts::InstalledFonts() ON_MemoryAllocationTracking disable_tracking(false); ON_SimpleArray device_list; #if defined(ON_OS_WINDOWS_GDI) - Internal_GetWindowsInstalledFonts(device_list); -#elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - Internal_GetAppleInstalledFonts(device_list); + ON_ManagedFonts::Internal_GetWindowsInstalledFonts(device_list); +#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + ON_ManagedFonts::Internal_GetAppleInstalledCTFonts(device_list); #endif if (device_list.Count() > 0) { diff --git a/opennurbs_font.h b/opennurbs_font.h index 6e2e3b17..0fe7be6c 100644 --- a/opennurbs_font.h +++ b/opennurbs_font.h @@ -357,7 +357,7 @@ public: commonly used fonts where using other glpyhs gives undesirable results.)OfI. Windows: = DWRITE_FONT_METRICS.capHeight - Apple: = NSFont. + Apple: = CTFontGetAscent(...) */ int AscentOfCapital() const; @@ -874,6 +874,10 @@ public: Error = 15 }; + static const wchar_t* OrientationToWideString( + ON_OutlineFigure::Orientation orientation + ); + /// /// ON_OutlineFigure::Type identifies the structure of the figure. /// @@ -952,6 +956,15 @@ public: */ double AreaEstimate() const; + + /* + Returns: + Bounding box area >= 0 + */ + double BoxArea() const; + + ON__UINT32 UnitsPerEM() const; + ON__UINT16 FigureIndex() const; unsigned int GetFigureCurves( @@ -975,13 +988,17 @@ public: bool ReverseFigure(); bool NegateY(); - + /* Description: Get a polyline approximation of the figure. + Parameters: tolerance - [in] - The value (ON_Outline.UnitsPerEM()) / 2048.0) gives nice results. + If tolerance > 0, that value is used. + Otherwise UnitsPerEM() / 256.0 is used, which gives course but + recognizable decent results for glyph outlines. + PointCallbackFunc - [in] called once for each point in the polyline context - [in] @@ -995,12 +1012,24 @@ public: void* context ) const; + double DefaultPolylineTolerance() const; + + static double DefaultPolylineTolerance( + double units_per_em + ); + + int WindingNumber( + ON_2fPoint winding_point + ) const; + /* Description: Get a polyline approximation of the figure. Parameters: tolerance - [in] - The value (ON_Outline.UnitsPerEM()) / 2048.0) gives nice results. + If tolerance > 0, that value is used. + Otherwise UnitsPerEM() / 256.0 is used, which gives course but + recognizable decent results for glyph outlines. points - [out] polyline points are appended to this array. Returns: @@ -1016,7 +1045,9 @@ public: Get a polyline approximation of the figure. Parameters: tolerance - [in] - The value (ON_Outline.UnitsPerEM()) / 2048.0) gives nice results. + If tolerance > 0, that value is used. + Otherwise UnitsPerEM() / 256.0 is used, which gives course but + recognizable decent results for glyph outlines. points - [out] polyline points are appended to this array. Returns: @@ -1032,7 +1063,9 @@ public: Get a polyline approximation of the figure. Parameters: tolerance - [in] - The value (ON_Outline.UnitsPerEM()) / 2048.0) gives nice results. + If tolerance > 0, that value is used. + Otherwise UnitsPerEM() / 256.0 is used, which gives course but + recognizable decent results for glyph outlines. points - [out] polyline points are appended to this array. Returns: @@ -1048,7 +1081,9 @@ public: Get a polyline approximation of the figure. Parameters: tolerance - [in] - The value (ON_Outline.UnitsPerEM()) / 2048.0) gives nice results. + If tolerance > 0, that value is used. + Otherwise UnitsPerEM() / 256.0 is used, which gives course but + recognizable decent results for glyph outlines. points - [out] polyline points are appended to this array. Returns: @@ -1063,7 +1098,7 @@ private: friend class ON_Outline; private: - ON__UINT32 m_reserved3 = 0; + ON__UINT32 m_units_per_em = 0; private: mutable ON_OutlineFigure::Orientation m_orientation = ON_OutlineFigure::Orientation::Unset; @@ -1135,6 +1170,11 @@ public: public: static const ON_Outline Unset; + /* + Default value for outer orientation when it is not explicitly specified. + */ + static const ON_OutlineFigure::Orientation DefaultOuterOrientation; + ON__UINT32 UnitsPerEM() const; @@ -1203,6 +1243,7 @@ public: ON_ClassArray< ON_SimpleArray< ON_Curve* > >& outline_curves ) const; + /* Returns: The bounding box of the outline curves. @@ -1248,6 +1289,32 @@ public: ON_OutlineFigure::Orientation outer_loop_orientation ); + /* + Returns: + ON_OutlineFigure::Orientation::Unset + Figures are not sorted. + ON_OutlineFigure::Orientation::CounterClockwise + Figures are sorted and outer figures are CCW. + ON_OutlineFigure::Orientation::Clockwise + Figures are sorted and outer figures are CW. + ON_OutlineFigure::Orientation::Error + Figure sorting failed. + */ + ON_OutlineFigure::Orientation SortedFigureOuterOrientation() const; + + /* + Returns: + ON_OutlineFigure::Orientation::Unset + Figures are not sorted. + ON_OutlineFigure::Orientation::CounterClockwise + Figures are sorted and inner figures are CCW. + ON_OutlineFigure::Orientation::Clockwise + Figures are sorted and inner figures are CW. + ON_OutlineFigure::Orientation::Error + Figure sorting failed. + */ + ON_OutlineFigure::Orientation SortedFigureInnerOrientation() const; + /* Returns: Type of figures in the outline. @@ -1274,7 +1341,11 @@ private: ON_OutlineFigure::Type m_figure_type = ON_OutlineFigure::Type::Unset; mutable ON__UINT8 m_bbox_status = 0; // 0 = unset, 1 = set, 7 = error - mutable ON__UINT8 m_sorted_status = 0; // 0 = unsorted, 1 = sorted, 7 = error; + // Unset = unsorted + // CounterClockwise: outer figures are CCW + // Clockwise: outer fitures are CW + // Error: error occured during sorting + mutable ON_OutlineFigure::Orientation m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Unset; ON__UINT8 m_reserved1 = 0; @@ -1344,12 +1415,16 @@ public: ON_Outline* destination_outline ); - bool EndOutline(); - void Clear(); ON_Outline* HarvestOutline(); + /* + Returns: + EndOutline(false,ON_Outline::DefaultOuterOrientation); + */ + bool EndOutline(); + /* Parameters: bNegatePointY - [in] @@ -1810,6 +1885,12 @@ public: ON_TextLog& text_log ) const; +#if defined(OPENNURBS_FREETYPE_SUPPORT) +// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. +// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). + + +public: /* Description: This is a debugging tool to test the code that starts with a font and @@ -1829,14 +1910,32 @@ public: handled by opennurbs, then true is returned and a message is printed to the log. */ - bool TestFaceCharMaps( + bool TestFreeTypeFaceCharMaps( ON_TextLog* text_log ) const; public: - + /* + Description: + If opennurbs is built with FreeType support then + FT_Face freetype_face = (FT_Face)glyph->FreeTypeFace() + will return a FreeType face that can be used to render the glyph. + Parameters: + font - [in] + Returns: + A value that can be cast as a FreeType FT_Face. + Example + const ON_Font* font = ...; + FT_Face freetype_face = (FT_Face)glyph->FreeTypeFace(font); + Remarks: + Many fonts do not have a glyph for a every UNICODE codepoint and font + substitution is required. If you want to get the freetype face + used for a specfic UNICODE codepoint, call ON_Font::CodepointFreeTypeFace(). + */ const ON__UINT_PTR FreeTypeFace() const; +#endif +public: /* Returns: Font glyph id. @@ -1847,8 +1946,15 @@ public: multiple Unicode code points map to the same glyph. For example, space an non-breaking space typically map to the same font glyph id. */ + unsigned int FontGlyphIndex() const; + + bool FontGlyphIndexIsSet() const; + + + ON_DEPRECATED_MSG("Use FontGlyphIndex()") const ON__UINT_PTR FontGlyphId() const; + ON_DEPRECATED_MSG("Use FontGlyphIndexIsSet()") bool FontGlyphIdIsSet() const; /* @@ -1928,13 +2034,14 @@ private: ON__UINT8 m_is_managed = 0; // 1 = managed glyph ON__UINT8 m_reserved1 = 0; ON__UINT16 m_reserved2 = 0; - ON__UINT_PTR m_font_glyph_id = 0; + ON__UINT32 m_reserved3 = 0; + ON__UINT32 m_font_glyph_index = 0; const class ON_Font* m_managed_font = nullptr; const class ON_FontGlyph* m_substitute = nullptr; private: - void Internal_SetFontGlyphId(ON__UINT_PTR font_glyph_id); + void Internal_SetFontGlyphIndex(unsigned int font_glyph_index); void Internal_CopyFrom(const ON_FontGlyph& src); static ON_FontGlyph* Internal_AllocateManagedGlyph(const ON_FontGlyph& src); bool Internal_GetPlatformSubstitute( @@ -2133,9 +2240,9 @@ public: WindowsFont = 2, /// - /// Set from an Apple NSFont of font name by ON_Font::SetFromAppleFont() - /// and PostScriptName() and FamilyName() match a font installed on an Apple device. - /// or iOS computer. + /// Set from an Apple CTFont. The PostScriptName() and FamilyName() match a + /// font installed on device running MacOS or iOS. The FaceName() matches + /// the "typeface" name shonw in the MacOS FontBook app. /// AppleFont = 3 }; @@ -2293,7 +2400,7 @@ public: ON_Font::Weight::Ultrabold = 0.5333 Apple font weight trait ON_Font::Weight::Heavy = 0.6667 Apple font weight trait Returns: - The Apple "NSFontWeightTrait" value that corresponds to the ON_Font::Weight enum value. + The Apple "WeightTrait" value that corresponds to the ON_Font::Weight enum value. */ static double AppleFontWeightTraitFromWeight( ON_Font::Weight font_weight @@ -2344,7 +2451,7 @@ public: /* Parameters: apple_font_weight_trait - [in] - Apple NSFontWeightTrait + Apple WeightTrait The valid value range is from -1.0 to 1.0. The value of 0.0 corresponds to the regular or medium font weight. */ static ON_Font::Weight WeightFromAppleFontWeightTrait( @@ -2743,7 +2850,7 @@ public: Parameters: postscript_name - [in] Windows: PostScript name = IDWriteFont.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME,...) - Apple: PostScript name = NSFont.fontName + Apple: PostScript name = CTFontCopyPostScriptName() / NSFont.fontName */ static const ON_Font* GetManagedFontFromPostScriptName( const char* postscript_name @@ -2753,19 +2860,26 @@ public: Parameters: postscript_name - [in] Windows: PostScript name = IDWriteFont.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME,...) - Apple: PostScript name = NSFont.fontName + Apple: PostScript name = CTFontCopyPostScriptName() / NSFont.fontName */ static const ON_Font* GetManagedFontFromPostScriptName( const wchar_t* postscript_name ); -#if defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - static const ON_Font* GetManagedFontFromAppleFont( - NSFont* apple_font, +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + static const ON_Font* GetManagedFontFromAppleCTFont( + CTFontRef apple_font, bool bAnnotationFont ); #endif +//#if defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +// static const ON_Font* GetManagedFontFromAppleNSFont( +// NSFont* apple_font, +// bool bAnnotationFont +// ); +//#endif + /* Returns: The managed font for this font. @@ -3172,39 +3286,6 @@ public: */ bool IsInstalledFont() const; -public: - - /* - Description: - If opennurbs is built with FreeType support then - FT_Face freetype_face = (FT_Face)ON_Font::FreeTypeFace(font) - will return a FreeType face that can be used to render the font. - Parameters: - font - [in] - Returns: - A value that can be cast as a FreeType FT_Face. - Example - const ON_Font* font = ...; - FT_Face freetype_face = (FT_Face)ON_Font::FreeTypeFace(font); - Remarks: - Many fonts do not have a glyph for a every UNICODE codepoint and font - substitution is required. If you want to get the freetype face - used for a specfic UNICODE codepoint, call ON_Font::CodepointFreeTypeFace(). - */ - static ON__UINT_PTR FreeTypeFace( - const ON_Font* font - ); - -private: - /* - Description: - Helper function used by destructor to deallocate memory used - by FreeType face - */ - static void DestroyFreeTypeFace( - const ON_Font* font - ); - public: ON_Font(); ~ON_Font() = default; @@ -3421,7 +3502,7 @@ public: as the PostScript name for at least 10 different Bahnschrift faces. Platform equivalents: - Apple: = NSFont.fontName + Apple: = CTFontCopyPostScriptName(...) / NSFont.fontName Windows: = IDWriteFont.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME,...) */ const ON_wString PostScriptName( @@ -3441,7 +3522,7 @@ public: Typically a font family has many faces. Platform equivalents: - Apple: = NSFont.familyName + Apple: = CTFontCopyFamilyName(...) / NSFont.familyName Windows: = IDWriteFontFamily.GetFamilyNames() NOTE WELL: This is NOT the Windows LOGFONT lfFaceName. @@ -3941,28 +4022,135 @@ public: #endif -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + public: - bool SetFromAppleFont ( - NSFont* apple_font, + /* + Parameters: + size - [in] + If size is not > 0, then the size is set to units_per_em. + */ + static CTFontRef AppleCTFontSetSize( + CTFontRef original, + CGFloat size, + bool bReleaseOriginal + ); + + static unsigned int AppleCTFontUnitsPerEm(CTFontRef apple_font); + + bool SetFromAppleCTFont ( + CTFontRef apple_font, bool bAnnotationFont ); - NSFont* AppleFont() const; + /* + Returns: + An Apple CTFontRef managed by the Apple platform. + Do not delete this font. + */ + CTFontRef AppleCTFont() const; + + /* + Returns: + An Apple CTFontRef managed by the Apple platform. + Do not delete this font. + */ + CTFontRef AppleCTFont(double point_size) const; - NSFont* AppleFont(double point_size) const; - static const ON_wString PostScriptNameFromAppleFont( - NSFont* apple_font - ); - - static const ON_wString FamilyNameFromAppleFont( - NSFont* apple_font + static CTFontRef AppleCTFont( + const wchar_t* name, + double point_size ); - static const ON_wString FaceNameFromAppleFont( - NSFont* apple_font - ); + static const ON_wString AppleCTFontPostScriptName( + CTFontRef apple_font + ); + + static const ON_wString AppleCTFontFamilyName( + CTFontRef apple_font + ); + + + /* + Description: + Apple CTFont, NSFont, and MacOS do not have "face names" as a font attribute. + This is the "typeface" name shown in the Apple FontBook Application + and the closest approximation to the Windows font face name. + The value from CTFontCopyName(...,kCTFontStyleNameKey) + */ + static const ON_wString AppleCTFontFaceName( + CTFontRef apple_font + ); + + /* + Description: + The Apple display name is used in some Apple apps + as a short description of the font. + When working in MacOS, the PostScript name is most reliable + way to uniquely identify a font. + */ + static const ON_wString AppleCTFontDisplayName( + CTFontRef apple_font + ); + + static ON_PANOSE1 AppleCTFontPANOSE1( + CTFontRef apple_font + ); + +#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) + // NSFont* appleNSFont = (__bridge NSFont*)(appleCTFont); + static NSFont* AppleTollFreeNSFont(CTFontRef appleCTFont); + + // CTFontRef appleCTFont = (__bridge CTFontRef)(appleNSFont); + static CTFontRef AppleTollFreeCTFont(NSFont* appleNSFont); +#endif + +#endif + +#if defined (OPENNURBS_FREETYPE_SUPPORT) +public: + /* + Description: + If opennurbs is built with FreeType support then + FT_Face freetype_face = (FT_Face)ON_Font::FreeTypeFace(font) + will return a FreeType face that can be used to render the font. + Parameters: + font - [in] + Returns: + A value that can be cast as a FreeType FT_Face. + Example + const ON_Font* font = ...; + FT_Face freetype_face = (FT_Face)ON_Font::FreeTypeFace(font); + Remarks: + Many fonts do not have a glyph for a every UNICODE codepoint and font + substitution is required. If you want to get the freetype face + used for a specfic UNICODE codepoint, call ON_Font::CodepointFreeTypeFace(). + */ + static ON__UINT_PTR FreeTypeFace( + const ON_Font* font + ); + +private: + /* + Description: + Helper function used by destructor to deallocate memory used + by FreeType face + */ + static void DestroyFreeTypeFace( + const ON_Font* font + ); + +public: + void DumpFreeType( + ON_TextLog& text_log + ) const; + +public: + static void DumpFreeTypeFace( + ON__UINT_PTR free_type_face_ptr, + ON_TextLog& text_log + ); #endif public: @@ -3970,18 +4158,11 @@ public: /* Parameters: postscript_name - [in] - From NSFont.fontName + From CTFontCopyPostScriptName(...) / NSFont.fontName Remarks: The "Apple Font Name" is the PostScript font name in the Mac OS "Font Book" application and in some other Apple documentation. - It is NSFont.fontName or the "fontManagerName" string in this for loop: - for (NSString* fontManagerName in[NSFontManager.sharedFontManager availableFonts]) - { - ... - } - It is the best choice of a string to pass as the "fontWithName" parameter - in the following call: - NSFont* apple_font = [NSFont fontWithName : apple_font_name size : pointSize]; + It is CTFontCopyPostScriptName(...) / NSFont.fontName. */ bool SetFromAppleFontName( const wchar_t* postscript_name @@ -3989,21 +4170,14 @@ public: /* Parameters: - postscript_name - [in] - From NSFont.fontName + postscript_name - [in] + From CTFontCopyPostScriptName(...) / NSFont.fontName point_size - [in] Pass 0.0 for annotation fonts Remarks: - The "Apple Font Name" is the PostScript name in the - Mac OS "Font Book" application and in some other Apple documentation. - It is NSFont.fontName or the "fontManagerName" string in this for loop: - for (NSString* fontManagerName in[NSFontManager.sharedFontManager availableFonts]) - { - ... - } - It is the best choice of a string to pass as the "fontWithName" parameter - in the following call: - NSFont* apple_font = [NSFont fontWithName : apple_font_name size : pointSize]; + The "Apple Font Name" is the PostScript font name in the + Mac OS "Font Book" application and in some other Apple documentation. + It is CTFontCopyPostScriptName(...) / NSFont.fontName. */ bool SetFromAppleFontName( const wchar_t* postscript_name, @@ -4095,6 +4269,12 @@ public: bool bDefaultIfEmpty ); + /* + Returns: + ON_Font::RichTextFontName(this,false); + */ + const ON_wString RichTextFontName() const; + /* Parameters: family_name - [in] @@ -4108,7 +4288,7 @@ public: or slope (Oblique, Italic, Upright). Generally, family names do NOT contain hyphens (like thos in PostScript names). - Apple: = NSFont.familyName + Apple: = CTFontCopyFamilyName() / NSFont.familyName Windows: = IDWriteFontFamily.GetFamilyNames() NOTE WELL: GDI LOGFONT.lfFaceName is NOT a font family name. @@ -4159,16 +4339,8 @@ public: void Dump( ON_TextLog& ) const; // for debugging - void DumpFreeType( - ON_TextLog& text_log - ) const; - - static void DumpFreeTypeFace( - ON__UINT_PTR free_type_face_ptr, - ON_TextLog& text_log - ); - #if defined(ON_OS_WINDOWS_GDI) +public: static void DumpLogfont( const LOGFONT* logfont, ON_TextLog& text_log @@ -4218,14 +4390,17 @@ public: ); #endif - -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - static void DumpNSFont( - NSFont* apple_font, + +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) +public: + static void DumpCTFont( + CTFontRef apple_font, ON_TextLog& text_log ); #endif +public: + // serialize definition to binary archive bool Write( ON_BinaryArchive& ) const; @@ -4366,12 +4541,8 @@ public: AnnotationFontCellHeight = 256, // Windows LOGFONT.lfHeight value (NOT A POINT SIZE) // This value is used on Apple platforms to get fonts used for rendering annotation. - // The size should be a power of 2. Ideally we want access to raw font and glyph - // design information but we cannot get this via NSFont. - // It appears that NSFont applies the scale factor - // AnnotationFontApplePointSize/(font design unit cell height) - // to the values in the font definitions (PostScript TrueType, OpenType,... font files). - // NSFont* apple_font = [NSFont fontWithName : size : ON_Font::Constants::AnnotationFontApplePointSize] + // The size should be a power of 2. Ideally we want access to the font and glyph + // design size returned by CTFontGetUnitsPerEm(). AnnotationFontApplePointSize = 256, // ON_Font::Constants::metric_char is the unicode code point value @@ -4512,7 +4683,7 @@ public: Description: This is a legacy function that traces it's heritage to Windows specific GDI LOGFONT code from 1995. Best to avoid it whenever possible. - Ideally, use an Windows IDWriteFont or Apple NSFont to crete an ON_Font that + Ideally, use an Windows IDWriteFont or Apple CTFont to create an ON_Font that references an installed font. Less ideally, use a complete LOGFONT structure. Parameters: windows_logfont_name - [in] @@ -4534,7 +4705,7 @@ public: Description: This is a legacy function that traces it's heritage to Windows specific GDI LOGFONT code from 1995. Best to avoid it whenever possible. - Ideally, use an Windows IDWriteFont or Apple NSFont to crete an ON_Font that + Ideally, use an Windows IDWriteFont or Apple CTFont to create an ON_Font that references an installed font. Less ideally, use a complete LOGFONT structure. Parameters: windows_logfont_name - [in] @@ -4831,6 +5002,8 @@ public: Returns: True if this is a known single stroke font. False otherwise. + See Also: + IsEngravingFont() */ bool IsSingleStrokeFont() const; @@ -4838,11 +5011,33 @@ public: Returns: True if this is a known double stroke font. False otherwise. + See Also: + IsEngravingFont() */ bool IsDoubleStrokeFont() const; + /* + Returns: + True if this is a known single stroke or double stroke font. + False otherwise. + See Also: + IsEngravingFont() + */ bool IsSingleStrokeOrDoubleStrokeFont() const; + /* + Description: + The outlines for an engraving font have single-stroke, double-stroke, + or perimeters desinged for path engraving. + These fonts behave poorly when used for filled font rendering + or creating solid extrusions. + The OrachTech 2 line fonts are examples of engraving fonts that + are not single or double stroke. + Returns: + True if the font is a known engraving font. + */ + bool IsEngravingFont() const; + unsigned char LogfontCharSet() const; bool SetLogfontCharSet( @@ -5044,7 +5239,7 @@ private: int m_windows_logfont_weight = 400; // 100 <= m_windows_logfont_weight <= 1000 double m_point_size = 0.0; // 0.0 indicates the annotation font size will be used. - double m_apple_font_weight_trait = 0.0; // = Apple NSFontWeightTrait value -1.0 <= m_apple_font_weight < 1.0, 0.0 = "normal" + double m_apple_font_weight_trait = 0.0; // = Apple WeightTrait value -1.0 <= m_apple_font_weight < 1.0, 0.0 = "normal" ON_Font::Weight m_font_weight = ON_Font::Weight::Normal; ON_Font::Style m_font_style = ON_Font::Style::Upright; // m_font_style corresponds to Windows LOGFONT.lfItalic field @@ -5068,7 +5263,7 @@ private: ON_wString m_locale_name; // Localized and English font PostScript name - // Apple: = NSFont.fontName + // Apple: = CTFontCopyPostScriptName() / NSFont.fontName // Windows: = IDWriteFont.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME,...) // NOTE WELL: // This is NOT the GDI LOGFONT.lfFaceName. @@ -5076,7 +5271,7 @@ private: ON_wString m_en_postscript_name; // Localized and English font family name - // Apple: = NSFont.familyName + // Apple: = CTFontCopyFamilyName() / NSFont.familyName // Windows: = IDWriteFontFamily.GetFamilyNames() // NOTE WELL: // This is NOT the GDI LOGFONT.lfFaceName. @@ -5084,7 +5279,7 @@ private: ON_wString m_en_family_name; // Localized and English font face name - // Apple: = not available + // Apple: = CTFontCopyName( ..., kCTFontStyleNameKey) // Windows: = IDWriteFont.GetFaceNames() // NOTE WELL: // This is NOT the GDI LOGFONT.lfFaceName. @@ -5177,7 +5372,7 @@ public: >0: Glyph index 0: failed */ - typedef ON__UINT_PTR (*ON_GetGlyphMetricsFuncType)( + typedef unsigned int (*ON_GetGlyphMetricsFuncType)( const class ON_FontGlyph* font_glyph, class ON_TextBox& glyph_metrics_in_font_design_units ); diff --git a/opennurbs_freetype.cpp b/opennurbs_freetype.cpp index 60d3d17c..032bcee8 100644 --- a/opennurbs_freetype.cpp +++ b/opennurbs_freetype.cpp @@ -23,9 +23,11 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif -#include "opennurbs_internal_glyph.h" - #if defined(OPENNURBS_FREETYPE_SUPPORT) +// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. +// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). + +#include "opennurbs_internal_glyph.h" // opennurbs uses FreeType to calculate font metric, glyph metric, and glyph outline information. @@ -419,8 +421,8 @@ private: const LOGFONT* logfont ); #endif -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - static ON_FreeTypeFace* Internal_CreateFaceFromAppleFont(NSFont* aFont); +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + static ON_FreeTypeFace* Internal_CreateFaceFromAppleFont(CTFontRef); #endif }; @@ -741,7 +743,7 @@ const ON_wString ON_FreeType::CharmapPlatformEncodingDescription( const FT_CharM return s; } -bool ON_FontGlyph::TestFaceCharMaps( +bool ON_FontGlyph::TestFreeTypeFaceCharMaps( ON_TextLog* text_log ) const { @@ -779,11 +781,11 @@ bool ON_FontGlyph::TestFaceCharMaps( return true; // nothing to test. } - const unsigned int glyph_id = static_cast(FontGlyphId()); - if ( 0 == glyph_id ) + const unsigned int glyph_index = FontGlyphIndex(); + if ( 0 == glyph_index ) { if (text_log) - text_log->Print("FontGlyphId is 0. Nothing to test.\n"); + text_log->Print("FontGlyphIndex is 0. Nothing to test.\n"); return true; // nothing to test. } @@ -851,7 +853,7 @@ bool ON_FontGlyph::TestFaceCharMaps( if (bHaveCharMap) { gid = FT_Get_Char_Index(face, char_code); - if (glyph_id != gid) + if (glyph_index != gid) rc = false; } else @@ -891,9 +893,9 @@ bool ON_FontGlyph::TestFaceCharMaps( L" -> glyph id %u", gid ); - if (glyph_id != gid) + if (glyph_index != gid) { - s += ON_wString::FormatToString(L"ERROR(expected glyph id %u)",glyph_id); + s += ON_wString::FormatToString(L"ERROR(expected glyph index %u)",glyph_index); } } text_log->Print(L"%ls\n", static_cast(s)); @@ -1363,15 +1365,14 @@ ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromWindowsFont( #endif -#if defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) -ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromAppleFont (NSFont* font) +ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromAppleFont (CTFontRef fontRef) { - if (font == nullptr) + if (nullptr == fontRef) return nullptr; - // determine file path for NSFont - CTFontRef fontRef = (__bridge CTFontRef) font; + // determine file path for CTFont CFURLRef fontURLRef = (CFURLRef) CTFontCopyAttribute (fontRef, kCTFontURLAttribute); NSURL* fontURL = (NSURL*) CFBridgingRelease (fontURLRef); @@ -1395,10 +1396,15 @@ ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromAppleFont (NSFont* font) int faceIndex = 0; for (;;) { - NSString* ftFamilyName = [NSString stringWithUTF8String: ftFace->family_name]; - NSString* ftStyleName = [NSString stringWithUTF8String: ftFace->style_name]; - NSString* nsStyleName = [font.fontDescriptor objectForKey: NSFontFaceAttribute]; - if ([font.familyName isEqualToString: ftFamilyName] && [nsStyleName isEqualToString: ftStyleName]) + const ON_wString appleFamilyName = ON_Font::AppleCTFontFamilyName(fontRef); + const ON_wString appleStyleName = ON_Font::AppleCTFontFaceName(fontRef); + const ON_wString ftFamilyName(ftFace->family_name); + const ON_wString ftStyleName(ftFace->style_name); + + if ( + ON_wString::EqualOrdinal(appleFamilyName, ftFamilyName, true) + && ON_wString::EqualOrdinal(appleStyleName, ftStyleName, true) + ) { rc->m_face = ftFace; return rc; @@ -1442,8 +1448,7 @@ ON_FreeTypeFace* ON_FreeType::CreateFace( f = ON_FreeType::Internal_CreateFaceFromWindowsFont(&logfont); #elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - NSFont* nsFont = font.AppleFont(); - f = ON_FreeType::Internal_CreateFaceFromAppleFont(nsFont); + f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont()); #endif // Create empty holder so this function doesn't repeatedly @@ -1489,6 +1494,14 @@ void ON_Font::DestroyFreeTypeFace( } } +const ON__UINT_PTR ON_FontGlyph::FreeTypeFace() const +{ + return + (nullptr == m_managed_font) + ? 0 + : ON_Font::FreeTypeFace(m_managed_font); +} + unsigned int ON_FreeTypeGetFontUnitsPerM( const ON_Font* font ) @@ -1517,6 +1530,10 @@ unsigned int ON_FreeTypeGetFontUnitsPerM( return 0; } +#if defined(ON_RUNTIME_APPLE) +#include "opennurbs_apple_nsfont.h" +#endif + void ON_FreeTypeGetFontMetrics( const ON_Font* font, ON_FontMetrics& font_unit_font_metrics @@ -1594,6 +1611,7 @@ void ON_FreeTypeGetFontMetrics( (0.5*h), (0.5*((double)ft_face->underline_thickness)) ); + } ///////////////////////////////////////////////////////////////////////////// @@ -1853,7 +1871,7 @@ int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineCubicToFunc( bool ON_FreeTypeLoadGlyph( ON__UINT_PTR ft_face_ptr, - ON__UINT_PTR ft_glyph_id_ptr, + unsigned int ft_glyph_index, bool bLoadRenderBitmap ) { @@ -1861,7 +1879,7 @@ bool ON_FreeTypeLoadGlyph( if (nullptr == ft_face) return false; - const FT_UInt font_glyph_id = (FT_UInt)ft_glyph_id_ptr; + const FT_UInt font_glyph_id = (FT_UInt)ft_glyph_index; if (0 == font_glyph_id) return false; @@ -1953,7 +1971,7 @@ bool ON_FreeTypeLoadGlyph( } // Returns font glyph id or 0 -ON__UINT_PTR ON_FreeTypeGetGlyphMetrics( +unsigned int ON_FreeTypeGetGlyphMetrics( const ON_FontGlyph* glyph, class ON_TextBox& glyph_metrics_in_font_design_units ) @@ -1975,18 +1993,33 @@ ON__UINT_PTR ON_FreeTypeGetGlyphMetrics( return 0; FT_Face face = (FT_Face)ft_face_as_uint; - - const unsigned int glyph_id - = glyph->FontGlyphIdIsSet() - ? (unsigned int)glyph->FontGlyphId() + + const unsigned int glyph_index + = glyph->FontGlyphIndexIsSet() + ? glyph->FontGlyphIndex() : ON_FreeType::GlyphId(face, glyph->CodePoint()); - if (0 == glyph_id) + if (0 == glyph_index) return 0; + + ////#if defined(ON_RUNTIME_APPLE) && defined(ON_DEBUG) + //// const unsigned int apple_glyph_id = ON_AppleNSFontGlyphIndex(font->AppleFont(), glyph->CodePoint()); + //// if (glyph_id != apple_glyph_id) + //// { + //// // In an Aug 20, 2018 test, anytime glyph_id != apple_glyph_id, the freetype value + //// // was incorrect. AppleGothic is a good test case. When freetype and ON_AppleNSFontGlyphIndex + //// // are different, freetype is referencing a glyph index that font book doesn't show + //// // (and is unable to get outlines) or one that is a solid box (and freetype delivers + //// // a square for the glyph instead of the correct glyph). + //// ON_ERROR("ON_AppleNSFontGlyphIndex() failed."); + //// ON_AppleNSFontGlyphIndex(font->AppleFont(), glyph->CodePoint()); + //// } + ////#endif + const bool bLoadRenderBitmap = false; // bLoadRenderBitmap = false means we load using FT_LOAD_NO_SCALE // This won't work for "tricky" font faces that render glyphs using composites. - if (false == ON_FreeTypeLoadGlyph(ft_face_as_uint, glyph_id, bLoadRenderBitmap)) + if (false == ON_FreeTypeLoadGlyph(ft_face_as_uint, glyph_index, bLoadRenderBitmap)) return 0; if ( nullptr == face->glyph) @@ -1996,7 +2029,7 @@ ON__UINT_PTR ON_FreeTypeGetGlyphMetrics( // face->glyph->metrics units are expressed in font design units. glyph_metrics_in_font_design_units = ON_TextBox_CreateFromFreeTypeGlyphMetrics(&face->glyph->metrics); - return glyph_id; + return glyph_index; } bool ON_FreeTypeGetGlyphOutline( @@ -2028,8 +2061,8 @@ bool ON_FreeTypeGetGlyphOutline( } } - ON__UINT_PTR glyph_index = glyph->FontGlyphId(); - if (glyph_index <= 0 || glyph_index > 0xFFFFFFFF) + const unsigned int glyph_index = glyph->FontGlyphIndex(); + if (glyph_index <= 0) return false; FT_Face ft_face = (FT_Face)(ON_Font::FreeTypeFace(font)); @@ -2055,7 +2088,8 @@ bool ON_FreeTypeGetGlyphOutline( rc = false; } - if ( false == outline.GlyphMetrics().IsSet() ) + // Generally, AddFreeTypeFiguresToOutline() set the outline glyph metrics. + if (false == outline.GlyphMetrics().IsSet()) outline.SetGlyphMetrics(glyph->FontUnitGlyphBox()); return rc; @@ -2128,526 +2162,10 @@ void ON_Font::DumpFreeTypeFace( return; } - -#else - -bool ON_FontGlyph::TestFaceCharMaps( - ON_TextLog* text_log -) const -{ - return true; -} - -void ON_FreeTypeGetFontMetrics( - const ON_Font* font, - ON_FontMetrics& font_unit_font_metrics -) -{ - font_unit_font_metrics = ON_FontMetrics::Unset; - return; -} - -ON__UINT_PTR ON_FreeTypeGetGlyphMetrics( - const ON_Font* font, - ON__UINT32 unicode_code_point, - class ON_TextBox& font_unit_glyph_box -) -{ - font_unit_glyph_box = ON_TextBox::Unset; - return 0; -} - -ON__UINT_PTR ON_Font::FreeTypeFace( - const ON_Font* font -) -{ - return 0; -} - -void ON_Font::DestroyFreeTypeFace( - const ON_Font* font -) -{} - - -bool ON_FreeTypeGetGlyphOutline( - const ON_FontGlyph* glyph, - bool bSingleStrokeFont, - double text_height, - ON_ClassArray< ON_SimpleArray< ON_Curve* > >& contours, - ON_BoundingBox* glyph_bbox, - ON_3dVector* glyph_advance -) -{ - return false; -} - -bool ON_FreeTypeLoadGlyph( - ON__UINT_PTR ft_face_ptr, - ON__UINT_PTR font_glyph_index_ptr, - bool bLoadRenderBitmap -) -{ - return false; -} - -void ON_Font::DumpFreeTypeFace( - ON__UINT_PTR free_type_face_ptr, - ON_TextLog& text_log -) -{ - return; -} - - -#endif - - -bool ON_FontGlyph::GetOutline( - bool bSingleStrokeFont, - class ON_Outline& outline -) const -{ - outline = ON_Outline::Unset; - const ON_Font* font = Font(); - if (nullptr == font) - return false; - - // When it comes to the difference between a single stroke and a double stroke font, - // users and programmers are confused most of the time. This code protects - // them from the consequences of that confusion. - ON_OutlineFigure::Type font_figure_type = font->OutlineFigureType(); - if (ON_OutlineFigure::Type::SingleStroke == font_figure_type) - bSingleStrokeFont = true; - else if (ON_OutlineFigure::Type::DoubleStroke == font_figure_type) - bSingleStrokeFont = false; - else if (bSingleStrokeFont) - font_figure_type = ON_OutlineFigure::Type::SingleStroke; - - if (nullptr != ON_Font::Internal_CustomGetGlyphOutlineFunc) - { - ON_Font::Internal_CustomGetGlyphOutlineFunc( - this, - bSingleStrokeFont, - outline - ); - } - else - { -#if defined(ON_OS_WINDOWS_GDI) - // Use Direct Write based tools - ON_WindowsDWriteGetGlyphOutline( - this, - font_figure_type, - outline - ); -#else -#if defined(OPENNURBS_FREETYPE_SUPPORT) - ON_FreeTypeGetGlyphOutline( - this, - font_figure_type, - outline - ); -#endif -#endif - } - - return outline.FigureCount() > 0; -} - -bool ON_FontGlyph::GetGlyphContours( - bool bSingleStrokeFont, - double height_of_capital, - ON_ClassArray< ON_SimpleArray< ON_Curve* > >& glyph_contours, - ON_BoundingBox* glyph_bbox, - ON_3dVector* glyph_advance -) const -{ - const ON_Font* font = Font(); - if (nullptr == font) - return false; - - ON_Outline outline; - GetOutline(bSingleStrokeFont, outline); - - const ON_FontMetrics fm = font->FontUnitFontMetrics(); - double scale = 1.0; - if (height_of_capital > 0.0 && height_of_capital < ON_UNSET_POSITIVE_FLOAT) - { - scale = fm.GlyphScale(height_of_capital); - } - else if ( - ON_UNSET_VALUE == height_of_capital - || ON_UNSET_POSITIVE_VALUE == height_of_capital - || ON_UNSET_FLOAT == height_of_capital - || ON_UNSET_POSITIVE_FLOAT == height_of_capital - ) - { - // returne results in font design units - scale = 1.0; - } - else - { - // normalized units. - const double font_UPM = font->FontUnitFontMetrics().AscentOfCapital(); - if ( font_UPM > 0.0 && font_UPM < ON_UNSET_POSITIVE_FLOAT ) - scale = ((double)ON_Font::AnnotationFontCellHeight) / font_UPM; - } - - unsigned int rc = outline.GetOutlineCurves( - scale, - true, // 3d curves - glyph_contours - ); - - const ON_TextBox glyph_metrics = outline.GlyphMetrics(); - - if (nullptr != glyph_advance) - *glyph_advance = scale * ON_3dVector(glyph_metrics.m_advance.i, glyph_metrics.m_advance.j, 0.0); - - if (nullptr != glyph_bbox) - *glyph_bbox = ON_BoundingBox( - scale*ON_3dPoint(glyph_metrics.m_bbmin.i, glyph_metrics.m_bbmin.j, 0.0), - scale*ON_3dPoint(glyph_metrics.m_bbmax.i, glyph_metrics.m_bbmax.j, 0.0) - ); - - return (rc > 0); - -} - -bool ON_Annotation::GetTextGlyphContours( - const ON_Viewport* vp, - const ON_DimStyle* dimstyle, - bool bApplyDimStyleDimScale, - bool bSingleStrokeFont, - ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours -) const -{ - const ON_TextContent* text_content = Text(); - if (nullptr == text_content) - return false; - - double text_scale = 0.0; - if (bApplyDimStyleDimScale && nullptr != dimstyle) - { - text_scale = dimstyle->DimScale(); - } - if (false == (text_scale > 0.0 && ON_IsValid(text_scale))) - text_scale = 1.0; - - - ON_Xform text_xform = ON_Xform::IdentityTransformation; - if (false == this->GetTextXform(vp, dimstyle, text_scale, text_xform)) - text_xform = ON_Xform::IdentityTransformation; - - const ON_Font* text_font = (nullptr != dimstyle) ? &dimstyle->Font() : nullptr; - - return text_content->GetGlyphContours(text_font, bSingleStrokeFont, text_xform, text_contours); -} - -bool ON_TextContent::GetGlyphContours( - const ON_Font* text_font, - bool bSingleStrokeFont, - double text_height, - ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours -) const -{ - ON_Xform text_xform = ON_Xform::IdentityTransformation; - - double scale = 1.0; - if (text_height > 0.0 && ON_IsValid(text_height) ) - { - if (nullptr == text_font) - text_font = &ON_Font::Default; - scale = text_font->FontMetrics().GlyphScale(text_height); - if (scale > 0.0) - text_xform = ON_Xform::DiagonalTransformation(scale); - } - - return this->GetGlyphContours( - text_font, - bSingleStrokeFont, - text_xform, - text_contours - ); -} - - -static const ON_FontGlyph* Internal_GetGlyphContours_SmallCapsGlyph( - const ON_FontGlyph* glyph -) -{ - if (nullptr == glyph || false == glyph->CodePointIsSet() ) - return nullptr; - const ON_FontGlyph* small_caps_glyph = nullptr; - const ON__UINT32 code_point = glyph->CodePoint(); - const ON__UINT32 upper_ordinal_code_point = ON_UnicodeMapCodePointOrdinal(ON_StringMapOrdinalType::UpperOrdinal, code_point); - if ( - upper_ordinal_code_point != code_point - && upper_ordinal_code_point >= 'A' - && ON_IsValidUnicodeCodePoint(upper_ordinal_code_point) - ) - { - small_caps_glyph = glyph->Font()->CodePointGlyph(upper_ordinal_code_point); - if (nullptr != small_caps_glyph) - { - if (glyph->Font() != small_caps_glyph->Font() || small_caps_glyph != small_caps_glyph->RenderGlyph(false)) - { - // do not permit font or glyph substitution when "small caps" are used. - small_caps_glyph = nullptr; - } - } - } - return small_caps_glyph; -} - -bool ON_FontGlyph::GetStringContours( - const wchar_t* text_string, - const ON_Font* font, - bool bSingleStrokeFont, - double text_height, - double small_caps_scale, - ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& string_contours -) -{ - // Dale Lear: https://mcneel.myjetbrains.com/youtrack/issue/RH-38183 - // Font substitution has to be used to get outlines for all code points. - // I rewrote this entire function to support use of multiple fonts in a single string - // to fix RH-38183. - const bool bUseReplacementCharacter = true; - - if (nullptr == text_string || 0 == text_string[0]) - return false; - - const ON_Font* primary_font = (nullptr != font) ? font->ManagedFont() : ON_Font::Default.ManagedFont(); - if (nullptr == primary_font) - return false; - - const ON_FontMetrics primary_fm = primary_font->FontMetrics(); - - double scale = (text_height > ON_ZERO_TOLERANCE && text_height < 1.e38) - ? primary_fm.GlyphScale(text_height) - : 0.0; - if (false == (scale > ON_ZERO_TOLERANCE && ON_IsValid(scale)) ) - { - text_height = 0.0; - scale = 1.0; - } - const double height_of_LF = scale*primary_fm.LineSpace(); - - if (false == (text_height > ON_ZERO_TOLERANCE && text_height < 1.e38)) - text_height = 0.0; - - const double small_caps_text_height - = (small_caps_scale > ON_ZERO_TOLERANCE && small_caps_scale < 1.0) - ? small_caps_scale*text_height - : text_height; - - ON_SimpleArray< const ON_FontGlyph* > glyph_list; - ON_TextBox text_box; - if (ON_FontGlyph::GetGlyphList( - text_string, - primary_font, - ON_UnicodeCodePoint::ON_LineSeparator, - glyph_list, - text_box) <= 0) - { - return false; - } - - double line_advance = 0.0; - ON_3dPoint glyph_base_point = ON_3dPoint::Origin; - - unsigned int glyph_count = glyph_list.UnsignedCount(); - for ( unsigned int gdex = 0; gdex < glyph_count; gdex++ ) - { - const ON_FontGlyph* glyph = glyph_list[gdex]; - if (nullptr == glyph) - continue; - if (glyph->IsEndOfLineCodePoint()) - { - line_advance += height_of_LF; - glyph_base_point.x = 0; - glyph_base_point.y = line_advance; - continue; - } - - glyph = glyph->RenderGlyph(bUseReplacementCharacter); - if (nullptr == glyph) - continue; - - double glyph_text_height = text_height; - - const ON_FontGlyph* small_caps_glyph = - (small_caps_text_height > 0.0 && small_caps_text_height < text_height) - ? Internal_GetGlyphContours_SmallCapsGlyph(glyph) - : glyph; - if (nullptr != small_caps_glyph) - { - glyph_text_height = small_caps_text_height; - glyph = small_caps_glyph; - } - - ON_BoundingBox glyph_contours_bbox = ON_BoundingBox::UnsetBoundingBox; - ON_3dVector glyph_contours_advance = ON_3dVector::ZeroVector; - ON_ClassArray< ON_SimpleArray< ON_Curve* > >& glyph_contours = string_contours.AppendNew(); - glyph->GetGlyphContours(bSingleStrokeFont, glyph_text_height, glyph_contours, &glyph_contours_bbox, &glyph_contours_advance); - - const ON_3dVector translate = glyph_base_point; - glyph_base_point.x += glyph_contours_advance.x; - - const int contour_count = glyph_contours.Count(); - - for (int li = 0; li < contour_count; li++) // contours per glyph - { - const int curve_count = glyph_contours[li].Count(); - for (int ci = 0; ci < curve_count; ci++) - { - if (nullptr != glyph_contours[li][ci]) - glyph_contours[li][ci]->Translate(translate); - } - } - } - - return true; -} - -bool ON_TextRun::GetGlyphContours( - const ON_Font* text_font, - bool bSingleStrokeFont, - const ON_Xform& text_xform, - ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& run_contours -) const -{ - const ON_TextRun& run = *this; - - const ON_Font* run_font = run.Font(); - if (nullptr == run_font) - { - run_font = text_font; - if (nullptr == run_font) - run_font = &ON_Font::Default; - } - - ON_Xform run_xf(text_xform); - - if (0.0 != run.m_offset.x || 0.0 != run.m_offset.y) - { - const ON_Xform run_offset(ON_Xform::TranslationTransformation(run.m_offset.x, run.m_offset.y, 0.0)); - run_xf = text_xform * run_offset; - } - - double run_height = run.TextHeight(); // Specified height of text in Model units - double I_height = run_font->FontMetrics().AscentOfCapital(); - double font_scale = run_height / I_height; // converts Font units to Model units, including text height - ON_Xform scale_xf(ON_Xform::DiagonalTransformation(font_scale)); - run_xf = run_xf * scale_xf; - - if (run.IsStacked() == ON_TextRun::Stacked::kStacked && nullptr != run.m_stacked_text) - { - const ON_TextRun* stacked_runs[2] = - { - run.m_stacked_text->m_top_run, - run.m_stacked_text->m_bottom_run, - }; - bool rc = false; - for (int i = 0; i < 2; i++) - { - if (nullptr == stacked_runs[i]) - continue; - if (stacked_runs[i]->GetGlyphContours( - run_font, - bSingleStrokeFont, - text_xform, - run_contours - )) - rc = true; - } - - //if (L'/' == run.m_stacked_text->m_separator) - //{ - // double h = 0.5 * I_height; - // double hs = (double)font->GetUnderscoreSize(); - // double l = run.m_advance.x / font_scale; - // DrawFracLine(*this, run_xf, 0.0, h, hs, l, color); - //} - return rc; - } - - - // run->UnicodeString() returns the raw string which may have unevaluated fields. - // run->DisplayString() returns the evaluated results of fields. - const int run_contours_count0 = run_contours.Count(); - bool rc = ON_FontGlyph::GetStringContours( - run.DisplayString(), - run_font, - bSingleStrokeFont, - 0.0, // text_height = 0.0 means get glyphs in openurbs normalized font size - 0.0, // small_caps_scale, - run_contours - ); - - const int run_contours_count1 = run_contours.Count(); - for (int gi = run_contours_count0; gi < run_contours_count1; gi++) - { - ON_ClassArray< ON_SimpleArray< ON_Curve* > >& countours = run_contours[gi]; - const int countour_count = countours.Count(); - for (int li = 0; li < countour_count; li++) - { - ON_SimpleArray< ON_Curve* >& curves = countours[li]; - const int curve_count = curves.Count(); - for (int ci = 0; ci < curve_count; ci++) - { - ON_Curve* curve = curves[ci]; - if (curve) - curve->Transform(run_xf); - } - } - } - - return rc; -} - -bool ON_TextContent::GetGlyphContours( - const ON_Font* text_font, - bool bSingleStrokeFont, - const ON_Xform& text_xform, - ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours -) const -{ - if (nullptr == text_font) - text_font = &ON_Font::Default; - - const ON_Xform xf = text_xform; - - const ON_TextRunArray* runs = TextRuns(false); - if (nullptr != runs) - { - const int runcount = runs->Count(); - for (int ri = 0; ri < runcount; ri++) - { - const ON_TextRun* run = (*runs)[ri]; - if (nullptr == run) - continue; - if (ON_TextRun::RunType::kText != run->Type() && ON_TextRun::RunType::kField != run->Type()) - continue; - - const ON_Font* run_font = run->Font(); - if (nullptr == run_font) - run_font = text_font; - - run->GetGlyphContours(run_font, bSingleStrokeFont, xf, text_contours); - } - } - - return false; -} - void ON_Font::DumpFreeType( ON_TextLog& text_log ) const { -#if defined(OPENNURBS_FREETYPE_SUPPORT) const ON_Font* managed_font = this->ManagedFont(); if (nullptr == managed_font) return; @@ -2658,5 +2176,7 @@ void ON_Font::DumpFreeType( text_log.Print("FreeType managed font = <%u>\n", managed_font->RuntimeSerialNumber()); } ON_Font::DumpFreeTypeFace((ON__UINT_PTR)(managed_font->m_free_type_face->m_face), text_log); -#endif } + +#endif + diff --git a/opennurbs_freetype.h b/opennurbs_freetype.h index 76ce411b..75182044 100644 --- a/opennurbs_freetype.h +++ b/opennurbs_freetype.h @@ -16,20 +16,16 @@ #if !defined(OPENNURBS_FREETYPE_INC_) #define OPENNURBS_FREETYPE_INC_ -#if defined(ON_COMPILER_MSC) -//&& defined(ON_64BIT_RUNTIME) -// freetype is not delivered in a 32-bit version. -// To disable freetype support, comment out the following define. -// To enable freetype support, define OPENNURBS_FREETYPE_SUPPORT +#if defined(OPENNURBS_FREETYPE_SUPPORT) +// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. +// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). -#define OPENNURBS_FREETYPE_SUPPORT - -#elif defined(ON_RUNTIME_APPLE) -// To disable freetype support, comment out the following define. -// To enable freetype support, define OPENNURBS_FREETYPE_SUPPORT - -#define OPENNURBS_FREETYPE_SUPPORT +#if defined(ON_COMPILER_MSC) ||defined(ON_RUNTIME_WIN) +#error FreeType is not used in Windows. It does not work as well as DirectWrite based tools. +#endif +#if defined(ON_RUNTIME_APPLE) +#error FreeType is not used in MacOS and iOS builds. It does not work as well as CTFont based code. #endif /* @@ -38,7 +34,7 @@ */ ON_DECL unsigned int ON_FreeTypeGetFontUnitsPerM( - const ON_Font* font + const class ON_Font* font ); /* @@ -49,8 +45,8 @@ Parameters: */ ON_DECL void ON_FreeTypeGetFontMetrics( - const ON_Font* font, - ON_FontMetrics& font_unit_font_metrics + const class ON_Font* font, + class ON_FontMetrics& font_unit_font_metrics ); /* @@ -60,12 +56,12 @@ Parameters: it is a "tricky" font. Returns: 0 if box was not set. - >0: font glyph id (or other non-zero value) when box is set + >0: font glyph index (or other non-zero value) when box is set */ ON_DECL -ON__UINT_PTR ON_FreeTypeGetGlyphMetrics( - const ON_FontGlyph* glyph, - ON_TextBox& glyph_metrics_in_font_design_units +unsigned int ON_FreeTypeGetGlyphMetrics( + const class ON_FontGlyph* glyph, + class ON_TextBox& glyph_metrics_in_font_design_units ); /* @@ -77,7 +73,7 @@ Parameters: */ ON_DECL bool ON_FreeTypeGetGlyphOutline( - const ON_FontGlyph* glyph, + const class ON_FontGlyph* glyph, ON_OutlineFigure::Type figure_type, class ON_Outline& outline ); @@ -97,9 +93,10 @@ Returns: ON_DECL bool ON_FreeTypeLoadGlyph( ON__UINT_PTR ft_face, - ON__UINT_PTR font_glyph_id, + unsigned int font_glyph_index, bool bLoadRenderBitmap ); +#endif #endif diff --git a/opennurbs_freetype_include.h b/opennurbs_freetype_include.h index b671aec0..13e7a5d7 100644 --- a/opennurbs_freetype_include.h +++ b/opennurbs_freetype_include.h @@ -224,6 +224,9 @@ // freetype includes directory in the system includes path. #if defined(OPENNURBS_FREETYPE_SUPPORT) +// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. +// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). + // Angle brackets are used on #include because if it fails, // the following #include FT_FREETYPE_H will fail, but in more mysterious ways. diff --git a/opennurbs_glyph_outline.cpp b/opennurbs_glyph_outline.cpp index db1e3f24..681bc813 100644 --- a/opennurbs_glyph_outline.cpp +++ b/opennurbs_glyph_outline.cpp @@ -21,6 +21,7 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif + #include "opennurbs_internal_glyph.h" #include "opennurbs_win_dwrite.h" @@ -116,48 +117,29 @@ void ON_Outline::Reverse() } } -static int Internal_CompareArea(ON_OutlineFigure*const*lhs, ON_OutlineFigure*const*rhs) -{ - if (lhs == rhs) - return 0; - - const ON_OutlineFigure* lhs_figure = *lhs; - const ON_OutlineFigure* rhs_figure = *rhs; - if (lhs_figure == rhs_figure) - return 0; - - const double lhs_area = fabs((*lhs)->AreaEstimate()); - const double rhs_area = fabs((*rhs)->AreaEstimate()); - - // biggest areas go first - if (lhs_area > rhs_area) - return -1; - if (lhs_area < rhs_area) - return 1; - - const unsigned lhs_fdex = lhs_figure->FigureIndex(); - const unsigned rhs_fdex = rhs_figure->FigureIndex(); - const int fdex_rc = (lhs_fdex < rhs_fdex) ? -1 : ((lhs_fdex > rhs_fdex) ? 1 : 0); - return fdex_rc; -} - void ON_Outline::SortFigures( ON_OutlineFigure::Orientation outer_loop_orientation ) { - if (0 != m_sorted_status) - return; - - const int figure_count = m_figures.Count(); - if (figure_count <= 0) - return; - if ( ON_OutlineFigure::Orientation::Clockwise != outer_loop_orientation && ON_OutlineFigure::Orientation::CounterClockwise != outer_loop_orientation ) return; + if (outer_loop_orientation == m_sorted_figure_outer_orientation) + return; + + if (ON_OutlineFigure::Orientation::Error == m_sorted_figure_outer_orientation) + return; // not sortable + + m_sorted_figure_outer_orientation = outer_loop_orientation; + + const int figure_count = m_figures.Count(); + if (figure_count <= 0) + return; + + const ON_OutlineFigure::Orientation inner_loop_orientation = (ON_OutlineFigure::Orientation::Clockwise == outer_loop_orientation) ? ON_OutlineFigure::Orientation::CounterClockwise @@ -166,174 +148,306 @@ void ON_Outline::SortFigures( ON_SimpleArray outer_figures(figure_count); ON_SimpleArray inner_figures(figure_count); ON_SimpleArray ignored_figures(figure_count); - ON_SimpleArray sorted_figures(figure_count); - - - bool bSortIgnoredFigures = false; - - bool bOuterInnerSortRequired = false; - - ON_BoundingBox prev_outer_box; - double prev_outer_area = -1.0; for (int i = 0; i < figure_count; i++) { ON_OutlineFigure* ptr = &m_figures[i]; - if ( - (ON_OutlineFigure::Type::Perimeter == ptr->FigureType() || ON_OutlineFigure::Type::Unknown == ptr->FigureType() || ON_OutlineFigure::Type::Unset == ptr->FigureType()) - && ON_IsValid(ptr->AreaEstimate()) - && (outer_loop_orientation == ptr->FigureOrientation() || inner_loop_orientation == ptr->FigureOrientation()) + if ( + (ON_OutlineFigure::Type::Perimeter == ptr->FigureType() + || ON_OutlineFigure::Type::Unknown == ptr->FigureType() + || ON_OutlineFigure::Type::Unset == ptr->FigureType()) ) { - const ON_BoundingBox bbox = ptr->BoundingBox(); - if (bbox.IsNotEmpty() && false == bbox.IsPoint()) + const double figure_area = ptr->AreaEstimate(); + const ON_BoundingBox figure_bbox = ptr->BoundingBox(); + const ON_OutlineFigure::Orientation figure_orientation = ptr->FigureOrientation(); + + if ( + 0.0 != figure_area && ON_IsValid(figure_area) + && figure_bbox.IsNotEmpty() && false == figure_bbox.IsPoint() + && (outer_loop_orientation == figure_orientation + || inner_loop_orientation == figure_orientation) + ) { if (outer_loop_orientation == ptr->FigureOrientation()) - { outer_figures.Append(ptr); - if (false == bOuterInnerSortRequired) - { - prev_outer_area = fabs(ptr->AreaEstimate()); - prev_outer_box = bbox; - sorted_figures.Append(ptr); - } - } else - { inner_figures.Append(ptr); - if (false == bOuterInnerSortRequired) - { - if ( - fabs(ptr->AreaEstimate()) > prev_outer_area - || false == prev_outer_box.Includes(ptr->BoundingBox()) - ) - { - bOuterInnerSortRequired = true; - } - else - { - sorted_figures.Append(ptr); - } - } - } - if (ignored_figures.UnsignedCount() > 0) - bSortIgnoredFigures = true; continue; } + + // Mark this figure has NotPerimeter because something about + // its path is not valid. This will prevent it from being + // used in meshing. + ptr->m_figure_type = ON_OutlineFigure::Type::NotPerimeter; } - if (ON_OutlineFigure::Type::Perimeter == ptr->FigureType() || ON_OutlineFigure::Type::Unknown == ptr->FigureType()) - ptr->m_figure_type = ON_OutlineFigure::Type::NotPerimeter; + // ignored figures are ones that are not a perimeter of an inner + // or outer boundary. ignored_figures.Append(ptr); } - if (0 == outer_figures.UnsignedCount()) + unsigned int outer_count = outer_figures.UnsignedCount(); + unsigned int inner_count = inner_figures.UnsignedCount(); + + if ( 0 == outer_count ) { - m_sorted_status = 1; + m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Error; return; } - if (false == bSortIgnoredFigures) + if (0 == inner_count ) { - if (false == bOuterInnerSortRequired) - { - m_sorted_status = 1; - return; - } - - if (1 == outer_figures.UnsignedCount()) - { - int outer_dex = 0; - ON_OutlineFigure* ptr = outer_figures[0]; - for (outer_dex = 0; outer_dex < figure_count; outer_dex++) - { - if (ptr == &m_figures[outer_dex]) - break; - } - if (outer_dex < 1 || outer_dex >= figure_count) - { - ON_ERROR("bug above"); - m_sorted_status = 7; - return; - } - ON_OutlineFigure tmp = m_figures[outer_dex]; - m_figures[outer_dex] = m_figures[0]; - m_figures[0] = tmp; - m_sorted_status = 1; - return; - } + return; } - if (bOuterInnerSortRequired) + const int winding_number_sign + = (ON_OutlineFigure::Orientation::Clockwise == outer_loop_orientation) + ? -1 + : 1; + + const unsigned int outer_and_inner_count = outer_count + inner_count; + + ON_SimpleArray sorted_figures(figure_count); + + while (sorted_figures.UnsignedCount() < outer_and_inner_count) { - sorted_figures.SetCount(0); - if (outer_figures.Count() <= 1 || inner_figures.Count() <= 0) + const unsigned int sorted_figures_count0 = sorted_figures.UnsignedCount(); + for (unsigned int outer_dex = 0; outer_dex < outer_count; outer_dex++) { - // we need to sort ignored_figures[] to the end but everything else is in order. - sorted_figures.Append(outer_figures.Count(), outer_figures.Array()); - sorted_figures.Append(inner_figures.Count(), inner_figures.Array()); - } - else - { - // complicated case with multiple outer figures and at least one inner figure. - outer_figures.QuickSort(Internal_CompareArea); - int dest_dex = outer_figures.Count() + inner_figures.Count(); - sorted_figures.SetCount(dest_dex); - dest_dex--; - for (int outer_dex = outer_figures.Count() - 1; outer_dex > 0 && dest_dex >= 0; outer_dex--) - { - ON_OutlineFigure* outer_figure = outer_figures[outer_dex]; - const double outer_area = fabs(outer_figure->AreaEstimate()); - const ON_BoundingBox outer_box = outer_figure->BoundingBox(); - for (int inner_dex = inner_figures.Count() - 1; inner_dex >= 0 && dest_dex >= 0; inner_dex--) - { - ON_OutlineFigure* inner_figure = inner_figures[inner_dex]; - if (nullptr == inner_figure) - continue; - const double inner_area = fabs(inner_figure->AreaEstimate()); - if (!(inner_area < outer_area)) - continue; - const ON_BoundingBox inner_box = inner_figure->BoundingBox(); - if (!outer_box.Includes(inner_box)) - continue; - sorted_figures[dest_dex--] = inner_figure; - inner_figures[inner_dex] = nullptr; - } - if (dest_dex >= 0) - sorted_figures[dest_dex--] = outer_figure; - } - for (int inner_dex = inner_figures.Count() - 1; inner_dex >= 0 && dest_dex >= 0; inner_dex--) + ON_OutlineFigure* outer_figure = outer_figures[outer_dex]; + if (nullptr == outer_figure) + continue; + outer_figures[outer_dex] = nullptr; + sorted_figures.Append(outer_figure); + + const double outer_area = fabs(outer_figure->AreaEstimate()); + const ON_BoundingBox outer_bbox = outer_figure->BoundingBox(); + + for (unsigned int inner_dex = 0; inner_dex < inner_count; inner_dex++) { ON_OutlineFigure* inner_figure = inner_figures[inner_dex]; if (nullptr == inner_figure) + continue; // inner_figures[inner_dex] assigned to previous outer figure + const double inner_area = fabs(inner_figure->AreaEstimate()); + if (false == (inner_area < outer_area)) continue; - sorted_figures[dest_dex--] = inner_figure; + const ON_BoundingBox inner_bbox = inner_figure->BoundingBox(); + if (false == outer_bbox.Includes(inner_bbox)) + continue; + const int wn0 = winding_number_sign * outer_figure->WindingNumber(inner_figure->m_points[0].m_point); + if (wn0 <= 0) + { + // Starting point of inner_figure is inside f and not inside outer_figure. + continue; + } + + // Based on area, bounding box, and inner starting point winding number, + // inner_figure is either inside of outer_figure or figures intersect. + for (unsigned int k = outer_dex + 1; k < outer_count; k++) + { + const ON_OutlineFigure* f = outer_figures[k]; + if (nullptr == f) + continue; + const double f_area = fabs(f->AreaEstimate()); + if (false == (inner_area < f_area)) + continue; + const ON_BoundingBox f_bbox = f->BoundingBox(); + if (false == (f_bbox.Includes(inner_bbox))) + continue; + + // f is an outer figure and based on area and bounding box, + // inner_figure could be inside of f as well. + // Use winding number tests to determine if outer_figure or f is a better choice. + + const int wn1 = winding_number_sign * f->WindingNumber(inner_figure->m_points[0].m_point); + if (wn1 > 0) + { + // Starting point of inner figure is inside f. + + if (wn0 <= 0) + { + + // Setting inner_figure = nullptr prevents it from being assigned to outer_figure. + inner_figure = nullptr; + break; + } + + // Starting point of inner_figure is inside f and inside outer_figure. + if (f_area < outer_area && outer_bbox.Includes(f_bbox)) + { + // Outer_figure is larger than f, so f is a better choice for this inner_figure. + // Setting inner_figure = nullptr prevents it from being assigned to outer_figure. + inner_figure = nullptr; + break; + } + + if (outer_area < f_area && f_bbox.Includes(outer_bbox)) + { + // f is larger than outer_figure, so outer_figure is a better choice than f. + // continue testing other outer figures + continue; + } + + // If we get here, either some of the figures (outer_figure, inner_figure, f) + // intersect or there is a later element in outer_figures[] that will be the + // best choice. + } + else + { + // Starting point of inner figure is not inside f. + if (wn0 > 0) + { + // Starting point of inner figure is inside outer_figure and not inside f. + // outer_figure is a better choice. + continue; + } + } + + // Ambiguous case and if the outline is valid there is another outer figure + // that will be better choice. The following hueristics can prevent + // further searching if f is more likely than outer_figure. + if (outer_bbox.Includes(f_bbox)) + { + // f is a smaller outer figure so we will prefer it. + // Setting inner_figure = nullptr prevents it from being assigned to outer_figure. + inner_figure = nullptr; + break; + } + + if ( + f->m_figure_index > outer_figure->m_figure_index + && inner_figure->m_figure_index > f->m_figure_index + ) + { + // The order of the figures in the source information is + // ... outer_figure, ..., f, ..., inner_figure. + // In general, this indicates f is a better guess that + // outer_figure. Setting inner_figure = nullptr here + // prevents it from being assigned to outer_figure. + // When we got around to testing f, we may find an + // outer_figure[index > k] that is an even better candidate + // than f. + inner_figure = nullptr; + break; + } + } + + if (nullptr == inner_figure) + { + // There are other out figures that are better candidates + // for holding inner_figure. + continue; + } + + // inner figure is inside outer_figure + inner_figures[inner_dex] = nullptr; + sorted_figures.Append(inner_figure); } - if (0 != dest_dex) + } + if (sorted_figures.UnsignedCount() == outer_and_inner_count) + break; // finished + + // typically we end up here when an outer figure is incorrect oriented + // ComicSans U+0048 (+/- glpyh) is one example of many. In ComicSans U+0048 + // the + and - figures are disjoint and have opposite orientations. + // Convert the largest unused inner figure to an outer figure. + + if (sorted_figures.UnsignedCount() <= sorted_figures_count0) + break; // no progress made + if (inner_count <= 0) + break; // nothing left to try. + + unsigned int largest_inner_area_index = ON_UNSET_UINT_INDEX; + double largest_inner_area = 0.0; + for (unsigned int inner_dex = 0; inner_dex < inner_count; inner_dex++) + { + ON_OutlineFigure* inner_figure = inner_figures[inner_dex]; + if (nullptr == inner_figure) + continue; // inner_figures[inner_dex] assigned to previous outer figure + const double inner_area = fabs(inner_figure->AreaEstimate()); + if (inner_area > largest_inner_area) { - ON_ERROR("figure sorting bug."); - m_sorted_status = 7; - return; + largest_inner_area = inner_area; + largest_inner_area_index = inner_dex; + } + } + + if (ON_UNSET_UINT_INDEX == largest_inner_area_index) + break; + ON_OutlineFigure* new_outer_figure = inner_figures[largest_inner_area_index]; + if (nullptr == new_outer_figure) + break; + + // convert new_outer_figure to an outer figure and continue sorting. + new_outer_figure->ReverseFigure(); + if (largest_inner_area_index+1 < inner_count) + { + inner_figures[largest_inner_area_index] = inner_figures[inner_count - 1]; + inner_figures[inner_count - 1] = nullptr; + } + inner_figures.SetCount(inner_count - 1); + outer_figures.Append(new_outer_figure); + outer_count = outer_figures.UnsignedCount(); + inner_count = inner_figures.UnsignedCount(); + } + + if (sorted_figures.UnsignedCount() != outer_and_inner_count) + { + // unable to sort outer and inner figures. + // Input is probably not valid. + // Use original order. + ON_ERROR("Unable to sort outer and inner figures."); + m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Error; + return; + } + + // Put single stroke and other non-perimeter figures on the end. + for (unsigned int k = 0; k < ignored_figures.UnsignedCount(); k++) + { + ON_OutlineFigure* f = ignored_figures[k]; + if (nullptr != f) + sorted_figures.Append(f); + } + + if (sorted_figures.Count() == figure_count) + { + for (int k = 0; k < figure_count; k++) + { + if (sorted_figures[k] != &m_figures[k]) + { + // Need to reorder m_figures[] array + ON_ClassArray tmp(figure_count); + for (int i = 0; i < figure_count; i++) + { + tmp.Append(*sorted_figures[i]); + } + m_figures = tmp; + break; } - sorted_figures[0] = outer_figures[0]; } } - ON_ClassArray tmp(m_figures.Count()); - for (int i = 0; i < sorted_figures.Count(); i++) - { - tmp.Append(*sorted_figures[i]); - } - for (int i = 0; i < ignored_figures.Count(); i++) - { - tmp.Append(*ignored_figures[i]); - } - - m_figures = tmp; - m_sorted_status = 1; return; } +ON_OutlineFigure::Orientation ON_Outline::SortedFigureOuterOrientation() const +{ + return m_sorted_figure_outer_orientation; +} + +ON_OutlineFigure::Orientation ON_Outline::SortedFigureInnerOrientation() const +{ + const ON_OutlineFigure::Orientation outer_orientation = SortedFigureOuterOrientation(); + if (ON_OutlineFigure::Orientation::CounterClockwise == outer_orientation) + return ON_OutlineFigure::Orientation::Clockwise; + if (ON_OutlineFigure::Orientation::Clockwise == outer_orientation) + return ON_OutlineFigure::Orientation::CounterClockwise; + + return outer_orientation; +} + + const ON_TextBox ON_Outline::GlyphMetrics() const { return m_glyph_metrics; @@ -462,14 +576,6 @@ bool ON_OutlineAccumulator::BeginGlyphOutline( return (1 == m_status); } -bool ON_OutlineAccumulator::EndOutline() -{ - return EndOutline( - false, - ON_OutlineFigure::Orientation::Unset - ); -} - bool ON_OutlineFigure::Internal_HasValidEnds( bool bLogErrors ) const @@ -684,6 +790,27 @@ ON__UINT16 ON_OutlineFigure::FigureIndex() const return m_figure_index; } +double ON_OutlineFigure::BoxArea() const +{ + const ON_BoundingBox bbox = BoundingBox(); + double bbox_area = 0.0; + if (bbox.IsNotEmpty()) + { + const double dx = bbox.m_max.x - bbox.m_min.x; + const double dy = bbox.m_max.y - bbox.m_min.y; + bbox_area = (dx >= 0.0 && dy >= 0.0) ? (dx*dy) : 0.0; + } + else + { + bbox_area = 0.0; + } + return bbox_area; +} + +ON__UINT32 ON_OutlineFigure::UnitsPerEM() const +{ + return m_units_per_em; +} double ON_OutlineFigure::AreaEstimate() const { @@ -820,7 +947,7 @@ double ON_OutlineFigure::AreaEstimate() const twice_area += Internal_DeltaArea(p0, p1); } p0 = p1; - p1 = cv[2]; + p1 = cv[3]; twice_area += Internal_DeltaArea(p0, p1); continue; } @@ -1221,6 +1348,7 @@ ON__UINT16 ON_Outline::Internal_AppendFigure( figure.m_points.Append((int)point_count, points); figure.m_points[0] = figure_start; figure.m_figure_type = m_figure_type; + figure.m_units_per_em = this->m_units_per_em; while (bDoubleStrokeReductionTest) { @@ -1309,7 +1437,7 @@ ON__UINT16 ON_Outline::Internal_AppendFigure( break; } - m_sorted_status = 0; + m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Unset; return figure.m_figure_index; } @@ -1705,16 +1833,23 @@ void Internal_OutlineFigureToPolyline::Internal_AddBezier( { if ( level > 1 ) { - double t = 0.25*(bez_cv[0].x + bez_cv[3].x) + 0.75*(bez_cv[1].x + bez_cv[2].x); // t = 2 * (cubic bezier x coordinate) - if ( fabs( bez_cv[0].x + bez_cv[3].x - 2.0*t ) <= m_2x_tolerance ) // testing "x" coordinate - { - t = 0.25*(bez_cv[0].y + bez_cv[3].y) + 0.75*(bez_cv[1].y + bez_cv[2].y); // t = 2*(cubic bezier midpoint y coordinate) - if ( fabs( bez_cv[0].y + bez_cv[3].y - 2.0*t ) <= m_2x_tolerance ) // testing "y" coordinate - { - // bez midpoint is within tolerance of the chord from bez[0] to bez[3] - this->AddPoint(bez_cv[3]); - return; - } + // x coordintat of bezier evaluated at t 0.5 is + // a = 1/8*bez_cv[0].x + 3/8*bez_cv[1].x + 3/8*bez_cv[2].x + 1/8*bez_cv[3].x + // + // x coordinate of midpoint of chord is + // b = (bez_cv[0].x + bez_cv[3].x)/2 + // + // a-b = c = 3/8(bez_cv[0].x - bez_cv[1].x - bez_cv[2].x + bez_cv[3].x) + // c <= tol if and only if 2*c <= 2*tol + // + if ( + fabs( 0.75*(bez_cv[0].x - bez_cv[1].x - bez_cv[2].x + bez_cv[3].x) ) <= m_2x_tolerance + && fabs( 0.75*(bez_cv[0].y - bez_cv[1].y - bez_cv[2].y + bez_cv[3].y) ) <= m_2x_tolerance + ) + { + // bez midpoint is within tolerance of the chord from bez[0] to bez[3] + this->AddPoint(bez_cv[3]); + return; } } } @@ -1780,6 +1915,22 @@ void Internal_OutlineFigureToPolyline::Internal_AddBezier( #endif +double ON_OutlineFigure::DefaultPolylineTolerance( + double units_per_em +) +{ + const double default_polyline_tolerance + = (units_per_em > 0.0 && units_per_em < ON_UNSET_POSITIVE_VALUE && units_per_em != ON_UNSET_FLOAT) + ? (units_per_em / 1024.0) + : 1.0; + return default_polyline_tolerance; +} + +double ON_OutlineFigure::DefaultPolylineTolerance() const +{ + return ON_OutlineFigure::DefaultPolylineTolerance(m_units_per_em); +} + unsigned int ON_OutlineFigure::GetPolyline( double tolerance, void(* PointCallbackFunc)(float x, float y, void *), @@ -1797,7 +1948,10 @@ unsigned int ON_OutlineFigure::GetPolyline( Internal_OutlineFigureToPolyline tp; tp.m_PointCallbackFunc = PointCallbackFunc; tp.m_context = context; - tp.m_2x_tolerance = 2.0*((tolerance > 0.0) ? tolerance : 1.0); + + const double default_tolerance = DefaultPolylineTolerance(); + + tp.m_2x_tolerance = 2.0*((tolerance > 0.0) ? tolerance : default_tolerance); ON_2fPoint bez_cv[4]; @@ -1884,6 +2038,128 @@ unsigned int ON_OutlineFigure::GetPolyline( return GetPolyline(tolerance, Internal_OutlineFigureToPolyline::PointCallbackFunc3d, &points); } +static void ON_Internal_FigureWindingNumberCallback(float x, float y, void *context) +{ + ((ON_WindingNumber*)context)->AddBoundary(ON_2dPoint(x, y)); +} + +int ON_OutlineFigure::WindingNumber( + ON_2fPoint winding_point +) const +{ + if (false == winding_point.IsValid()) + return 0; + + const ON_BoundingBox bbox = BoundingBox(); + if (false == (winding_point.x >= bbox.m_min.x && winding_point.x <= bbox.m_max.x)) + return false; + if (false == (winding_point.y >= bbox.m_min.y && winding_point.y <= bbox.m_max.y)) + return false; + + const ON_OutlineFigurePoint* a = m_points.Array(); + const unsigned int c = m_points.UnsignedCount(); + if (nullptr == a || c <= 0) + return 0; + + ON_WindingNumber wn; + wn.SetWindingPoint(winding_point.x,winding_point.y); + + Internal_OutlineFigureToPolyline tp; + tp.m_PointCallbackFunc = ON_Internal_FigureWindingNumberCallback; + tp.m_context = &wn; + tp.m_2x_tolerance = 2.0; + + ON_2fPoint bez_cv[4]; + + ON_2fPoint bbox_min, bbox_max; + + for (unsigned int i = 0; i < c; i++) + { + if (tp.m_point_count > 0) + { + if ( + ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i].m_point_type + && i + 1 < c + && ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i + 1].m_point_type + ) + { + bez_cv[0] = tp.m_prev_point; + bez_cv[1] = a[i++].m_point; + bez_cv[2] = a[i].m_point; + + bbox_min = bez_cv[0]; + bbox_max = bbox_min; + for (int j = 1; j < 2; j++) + { + if (bez_cv[j].x < bbox_min.x) + bbox_min.x = bez_cv[j].x; + else if (bez_cv[j].x > bbox_max.x) + bbox_max.x = bez_cv[j].x; + if (bez_cv[j].y < bbox_min.y) + bbox_min.y = bez_cv[j].y; + else if (bez_cv[j].y > bbox_max.y) + bbox_max.y = bez_cv[j].y; + } + + if (bbox.m_min.y <= winding_point.y && winding_point.y <= bbox.m_max.y) + { + if (bbox.m_min.x <= winding_point.x && winding_point.x <= bbox.m_max.x) + tp.AddQuadraticBezier(bez_cv); + else + wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point)); + } + tp.m_prev_point = bez_cv[2]; + continue; + } + + if ( + ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i].m_point_type + && i + 2 < c + && ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 1].m_point_type + && ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 2].m_point_type + ) + { + bez_cv[0] = tp.m_prev_point; + bez_cv[1] = a[i++].m_point; + bez_cv[2] = a[i++].m_point; + bez_cv[3] = a[i].m_point; + + bbox_min = bez_cv[0]; + bbox_max = bbox_min; + for (int j = 1; j < 3; j++) + { + if (bez_cv[j].x < bbox_min.x) + bbox_min.x = bez_cv[j].x; + else if (bez_cv[j].x > bbox_max.x) + bbox_max.x = bez_cv[j].x; + if (bez_cv[j].y < bbox_min.y) + bbox_min.y = bez_cv[j].y; + else if (bez_cv[j].y > bbox_max.y) + bbox_max.y = bez_cv[j].y; + } + + if (bbox.m_min.y <= winding_point.y && winding_point.y <= bbox.m_max.y) + { + if (bbox.m_min.x <= winding_point.x && winding_point.x <= bbox.m_max.x) + tp.AddCubicBezier(bez_cv); + else + wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point)); + } + tp.m_prev_point = bez_cv[3]; + continue; + } + } + + if ( tp.m_point_count > 0 ) + wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point)); + + tp.AddPoint(a[i].m_point); + } + + return wn.WindingNumber(); +} + + bool ON_Outline::IsValidOutline( bool bLogErrors ) const @@ -1904,6 +2180,11 @@ bool ON_Outline::IsValidOutline( return true; } +bool ON_OutlineAccumulator::EndOutline() +{ + return EndOutline(false, ON_Outline::DefaultOuterOrientation); +} + bool ON_OutlineAccumulator::EndOutline( bool bNegatePointY, ON_OutlineFigure::Orientation orientation @@ -2036,12 +2317,19 @@ bool ON_OutlineAccumulator::AppendQuadraticBezier( const ON_2fPoint cv0 = m_figure_current.m_point; + if (cv0 == cv1 && cv0 == cv2) + { + // silently skip empty segments + return false; + } + if (cv0 == cv1 || cv2 == cv1) return AppendLine(cv2); if (cv0 == cv2) { - ON_ERROR("Degenerate quadratic bezier segment."); + // silently skip degenerate segments + //ON_ERROR("Degenerate quadratic bezier segment."); return false; } @@ -2157,9 +2445,22 @@ bool ON_OutlineAccumulator::AppendCubicBezier( if (cv0 == cv1 && cv0 == cv2 && cv0 == cv3) { - if (m_figure_current.m_point == cv1) + // silently skip empty segments + return false; + } + + if (cv0 == cv3) + { + // Check for degenerate cubic (all cvs on a line segment) + const double triangle_area2x + = cv0.y*(cv2.x - cv1.x) + + cv1.y*(cv0.x - cv2.x) + + cv2.y*(cv1.x - cv0.x); + const double tol = UnitsPerEM() / 16384.0; + if (fabs(triangle_area2x) <= tol*tol) { - // silently skip constant segments + // silently skip degenerate segments + //ON_ERROR("Degenerate cubic bezier segment."); return false; } } @@ -2637,3 +2938,126 @@ bool ON_OutlineAccumulator::Internal_AccumulatePoint( // ready for the next point. return true; } + +bool ON_FontGlyph::GetOutline( + bool bSingleStrokeFont, + class ON_Outline& outline +) const +{ + outline = ON_Outline::Unset; + const ON_Font* font = Font(); + if (nullptr == font) + return false; + + // When it comes to the difference between a single stroke and a double stroke font, + // users and programmers are confused most of the time. This code protects + // them from the consequences of that confusion. + ON_OutlineFigure::Type font_figure_type = font->OutlineFigureType(); + if (ON_OutlineFigure::Type::SingleStroke == font_figure_type) + bSingleStrokeFont = true; + else if (ON_OutlineFigure::Type::DoubleStroke == font_figure_type) + bSingleStrokeFont = false; + else if (bSingleStrokeFont) + font_figure_type = ON_OutlineFigure::Type::SingleStroke; + + bool rc = false; + if (nullptr != ON_Font::Internal_CustomGetGlyphOutlineFunc) + { + rc = ON_Font::Internal_CustomGetGlyphOutlineFunc( + this, + bSingleStrokeFont, + outline + ); + } + else + { +#if defined(ON_OS_WINDOWS_GDI) + // Use Windows Direct Write based tools + rc = ON_WindowsDWriteGetGlyphOutline( + this, + font_figure_type, + outline + ); +#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + // Use Apple Core Text based tools + rc = ON_AppleFontGetGlyphOutline( + this, + font_figure_type, + outline + ); +#elif defined(OPENNURBS_FREETYPE_SUPPORT) + // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. + // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). + // Use freetype based tools (least reliable results) + rc = ON_FreeTypeGetGlyphOutline( + this, + font_figure_type, + outline + ); +#endif + } + + return (rc || outline.FigureCount() > 0); +} + +bool ON_FontGlyph::GetGlyphContours( + bool bSingleStrokeFont, + double height_of_capital, + ON_ClassArray< ON_SimpleArray< ON_Curve* > >& glyph_contours, + ON_BoundingBox* glyph_bbox, + ON_3dVector* glyph_advance +) const +{ + const ON_Font* font = Font(); + if (nullptr == font) + return false; + + ON_Outline outline; + GetOutline(bSingleStrokeFont, outline); + + const ON_FontMetrics fm = font->FontUnitFontMetrics(); + double scale = 1.0; + if (height_of_capital > 0.0 && height_of_capital < ON_UNSET_POSITIVE_FLOAT) + { + scale = fm.GlyphScale(height_of_capital); + } + else if ( + ON_UNSET_VALUE == height_of_capital + || ON_UNSET_POSITIVE_VALUE == height_of_capital + || ON_UNSET_FLOAT == height_of_capital + || ON_UNSET_POSITIVE_FLOAT == height_of_capital + ) + { + // returne results in font design units + scale = 1.0; + } + else + { + // normalized units. + const double font_UPM = font->FontUnitFontMetrics().AscentOfCapital(); + if ( font_UPM > 0.0 && font_UPM < ON_UNSET_POSITIVE_FLOAT ) + scale = ((double)ON_Font::AnnotationFontCellHeight) / font_UPM; + } + + unsigned int rc = outline.GetOutlineCurves( + scale, + true, // 3d curves + glyph_contours + ); + + const ON_TextBox glyph_metrics = outline.GlyphMetrics(); + + if (nullptr != glyph_advance) + *glyph_advance = scale * ON_3dVector(glyph_metrics.m_advance.i, glyph_metrics.m_advance.j, 0.0); + + if (nullptr != glyph_bbox) + *glyph_bbox = ON_BoundingBox( + scale*ON_3dPoint(glyph_metrics.m_bbmin.i, glyph_metrics.m_bbmin.j, 0.0), + scale*ON_3dPoint(glyph_metrics.m_bbmax.i, glyph_metrics.m_bbmax.j, 0.0) + ); + + return (rc > 0); +} + + + diff --git a/opennurbs_hatch.cpp b/opennurbs_hatch.cpp index 1013b02a..50f3aeab 100644 --- a/opennurbs_hatch.cpp +++ b/opennurbs_hatch.cpp @@ -339,7 +339,7 @@ static bool Internal_UseHatchReadV5( return false; // There are 4 possible Feb 26 version numbers depending on the build system - // archive.Archive3dmVersion() % 4 = (0,1,2,3 identifies the build system = OPENNURBS_VERSION_BRANCH) + // archive.Archive3dmVersion() % 4 = (0=developer,1=windows build system,2 = mac build system, 3=unused) const unsigned int Feb_26_2016_opennurbs_version_number0 = 2348833956; const unsigned int Feb_26_2016_opennurbs_version_number3 = 2348833956+3; @@ -1762,10 +1762,10 @@ bool ON_Hatch::Transform( const ON_Xform& xform) } int rc = m_plane.Transform( xform); - ON_3dVector x = m_plane.xaxis; - x.Transform(xform); - double scale = x.Length() * PatternScale(); - SetPatternScale(scale); + //ON_3dVector x = m_plane.xaxis; + //x.Transform(xform); + //double scale = x.Length() * PatternScale(); + //SetPatternScale(scale); UnrotateHatch(this); @@ -1774,6 +1774,20 @@ bool ON_Hatch::Transform( const ON_Xform& xform) return rc; } +bool ON_Hatch::ScalePattern(ON_Xform xform) +{ + ON_3dVector v = m_plane.xaxis; + v.Transform(xform); + + double l = v.Length(); + if (v.Unitize()) + { + m_pattern_scale *= l; + return true; + } + return false; +} + ON_Brep* ON_Hatch::BrepForm(ON_Brep* brep) const { if (brep) @@ -1993,13 +2007,12 @@ void ON_Hatch::SetBasePoint(ON_2dPoint basepoint) void ON_Hatch::SetBasePoint(ON_3dPoint point) { - m_basepoint = point; + m_plane.ClosestPointTo(point, &m_basepoint.x, &m_basepoint.y); } ON_3dPoint ON_Hatch::BasePoint() const { - const ON_3dPoint point(m_basepoint); - return point; + return m_plane.PointAt(m_basepoint.x, m_basepoint.y); } ON_2dPoint ON_Hatch::BasePoint2d() const diff --git a/opennurbs_hatch.h b/opennurbs_hatch.h index 05aa1587..ab70c685 100644 --- a/opennurbs_hatch.h +++ b/opennurbs_hatch.h @@ -629,6 +629,21 @@ public: */ bool Transform( const ON_Xform&) override; + /* + Description: + Scales the hatch's pattern by a 4x4 xform matrix + Parameters: + [in] xform - An ON_Xform with the transformation information + Returns: + true = Success + false = Failure + Remarks: + The hatch pattern scale is multiplied by the change in length of a + unit vector in the hatch plane x direction when that vector is + scaled by the input xform + */ + bool ScalePattern(ON_Xform xform); + /* Description: If possible, BrepForm() creates a brep form of the diff --git a/opennurbs_internal_V2_annotation.cpp b/opennurbs_internal_V2_annotation.cpp index c200cf8c..9c379ecc 100644 --- a/opennurbs_internal_V2_annotation.cpp +++ b/opennurbs_internal_V2_annotation.cpp @@ -1228,7 +1228,7 @@ bool ON_OBSOLETE_V5_Annotation::Read( ON_BinaryArchive& file ) { if (bIsText) { - if (dim_style_index0 >= 0 && dim_style_index0 == dim_style_index1) + if (dim_style_index0 > ON_UNSET_INT_INDEX && dim_style_index0 == dim_style_index1) dim_style_index = dim_style_index1; else { @@ -1244,7 +1244,7 @@ bool ON_OBSOLETE_V5_Annotation::Read( ON_BinaryArchive& file ) else { // not text - if (dim_style_index0 >= 0 && dim_style_index0 == dim_style_index2) + if (dim_style_index0 > ON_UNSET_INT_INDEX && dim_style_index0 == dim_style_index2) dim_style_index = dim_style_index2; else { @@ -3651,7 +3651,13 @@ ON_2dPoint ON_OBSOLETE_V5_DimAngular::Dim2dPoint( int point_index ) const ON_3dPoint ON_OBSOLETE_V5_DimAngular::Dim3dPoint( int point_index ) const { ON_2dPoint p2 = Dim2dPoint(point_index); - return (ON_UNSET_VALUE == p2.x) ? ON_3dPoint::UnsetPoint : m_plane.PointAt(p2.x,p2.y); + if (ON_UNSET_VALUE == p2.x) + return ON_3dPoint::UnsetPoint; + + ON_3dPoint p = m_plane.PointAt(p2.x, p2.y); + return p; + + //return (ON_UNSET_VALUE == p2.x) ? ON_3dPoint::UnsetPoint : m_plane.PointAt(p2.x,p2.y); } diff --git a/opennurbs_internal_Vx_annotation.cpp b/opennurbs_internal_Vx_annotation.cpp index 3485b084..b74c8d5d 100644 --- a/opennurbs_internal_Vx_annotation.cpp +++ b/opennurbs_internal_Vx_annotation.cpp @@ -423,7 +423,7 @@ ON_Text* ON_Text::CreateFromV5TextObject( const ON::TextVerticalAlignment valign = ON::TextVerticalAlignmentFromV5Justification(just); ON_Plane plane = V5_text_object.Plane(); - const double h = V5_text_object.Height(); + const double v5height = V5_text_object.Height(); ON_wString txt = V5_text_object.TextFormula(); if (txt.IsEmpty()) txt = V5_text_object.TextValue(); @@ -442,8 +442,8 @@ ON_Text* ON_Text::CreateFromV5TextObject( { double dimscale = dim_style.DimScale(); double dsht = dim_style.TextHeight(); - double widthscale = h * dimscale / dsht; - double width = (wrapwidth + (h * 0.1)) * widthscale; + double widthscale = v5height * dimscale / dsht; + double width = (wrapwidth + (v5height * 0.1)) * widthscale; V6_text_object->Create(newtxt, &dim_style, plane, true, width, 0.0); } else @@ -452,13 +452,47 @@ ON_Text* ON_Text::CreateFromV5TextObject( // These override settings currently get erased by // ON_Annotation::SetDimensionStyleId(ON_UUID dimstyle_id) // which deletes any existing override style - if (h > 0.0 && h != dim_style.TextHeight()) - V6_text_object->SetTextHeight(&dim_style, h); + + if (v5height > 0.0 && v5height != dim_style.TextHeight()) + V6_text_object->SetTextHeight(&dim_style, v5height); if (halign != dim_style.TextHorizontalAlignment()) V6_text_object->SetTextHorizontalAlignment(&dim_style, halign); if (valign != dim_style.TextVerticalAlignment()) V6_text_object->SetTextVerticalAlignment(&dim_style, valign); + bool setmask = false; + ON_TextMask mask = dim_style.TextMask(); + bool drawmask = V5_text_object.DrawTextMask(); + if (drawmask != mask.DrawTextMask()) + { + mask.SetDrawTextMask(drawmask); + setmask = true; + } + if (drawmask) + { + double mb = V5_text_object.MaskOffsetFactor() * v5height; + if (mb != mask.MaskBorder()) + { + mask.SetMaskBorder(mb); + setmask = true; + } + ON_TextMask::MaskType mt = mask.MaskFillType(); + int mc = V5_text_object.MaskColorSource(); + if ((mt == ON_TextMask::MaskType::BackgroundColor && mc != 0) || + (mt == ON_TextMask::MaskType::MaskColor && mc != 1)) + { + mask.SetMaskFillType(mc == 0 ? ON_TextMask::MaskType::BackgroundColor : ON_TextMask::MaskType::MaskColor); + setmask = true; + } + if (V5_text_object.MaskColor() != mask.MaskColor()) + { + mask.SetMaskColor(V5_text_object.MaskColor()); + setmask = true; + } + } + if (setmask) + V6_text_object->SetTextMask(&dim_style, mask); + if (V5_text_object.m_annotative_scale && annotation_context->AnnotationSettingsAreSet()) { @@ -599,11 +633,14 @@ ON_OBSOLETE_V5_TextObject* ON_OBSOLETE_V5_TextObject::CreateFromV6TextObject( v5_text_entity_scale = annotation_settings.WorldViewTextScale(); } - const double dim_style_scale = V6_text_object.DimScale(&parent_dim_style); - if (ON_IsValid(dim_style_scale) && dim_style_scale > 0.0 && v5_text_entity_scale > 0.0 && dim_style_scale != v5_text_entity_scale) + if (annotation_settings.IsModelSpaceAnnotationScalingEnabled()) { - const double scale = dim_style_scale / v5_text_entity_scale; - V5_text_object->m_textheight *= scale; + const double dim_style_scale = V6_text_object.DimScale(&parent_dim_style); + if (ON_IsValid(dim_style_scale) && dim_style_scale > 0.0 && v5_text_entity_scale > 0.0 && dim_style_scale != v5_text_entity_scale) + { + const double scale = dim_style_scale / v5_text_entity_scale; + V5_text_object->m_textheight *= scale; + } } } } @@ -1134,6 +1171,8 @@ ON_DimAngular* ON_DimAngular::CreateFromV5DimAngular( V6_dim_angle->SetUseDefaultTextPoint(!V5_dim_angle.UserPositionedText()); ON_wString usrtext = V5_dim_angle.TextFormula(); usrtext.Replace(L"\\", L"\\\\"); + if (usrtext.Length() > 1 && usrtext[usrtext.Length()-1] == ON_DegreeSymbol) + usrtext.SetLength(usrtext.Length() - 1); V6_dim_angle->SetUserText(usrtext.Array()); // updates any m_overrides to be current and updates content hash. @@ -1396,6 +1435,10 @@ ON_DimOrdinate* ON_DimOrdinate::CreateFromV5DimOrdinate( V6_dim_ordinate->SetDetailMeasured(extra->DetailMeasured()); V6_dim_ordinate->SetDistanceScale(extra->DistanceScale()); } + + ON_wString usrtext = v5_dim_ordinate.TextFormula(); + usrtext.Replace(L"\\", L"\\\\"); + V6_dim_ordinate->SetUserText(usrtext.Array()); // updates any m_overrides to be current and updates content hash. parent_dim_style.ContentHash(); diff --git a/opennurbs_internal_glyph.h b/opennurbs_internal_glyph.h index 7db12b69..18e272a4 100644 --- a/opennurbs_internal_glyph.h +++ b/opennurbs_internal_glyph.h @@ -50,10 +50,11 @@ public: ); #if defined(ON_OS_WINDOWS_GDI) - - static void Internal_GetWindowsInstalledFonts(ON_SimpleArray&); +#endif +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + static void Internal_GetAppleInstalledCTFonts(ON_SimpleArray& platform_font_list); #endif // sorts nulls to end of lists @@ -64,9 +65,9 @@ public: /* Returns: 0: failure - >0: success font glyph id + >0: success font glyph index */ - static ON__UINT_PTR GetGlyphMetricsInFontDesignUnits( + static unsigned int GetGlyphMetricsInFontDesignUnits( const class ON_Font* font, ON__UINT32 unicode_code_point, class ON_TextBox& glyph_metrics_in_font_design_units @@ -171,7 +172,7 @@ Returns: 0: failed */ ON_DECL -ON__UINT_PTR ON_WindowsDWriteGetGlyphMetrics( +unsigned int ON_WindowsDWriteGetGlyphMetrics( const ON_FontGlyph* glyph, ON_TextBox& glyph_metrics ); @@ -191,14 +192,10 @@ bool ON_WindowsDWriteGetGlyphOutline( ); #endif -#if defined(ON_RUNTIME_APPLE) && defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) /* Parameters: glyph - [in] - knownUPM - [in] - The cell height in design units is not avaialble from the Apple NSFont SDK. - If you known it, pass it in. Otherwise pass 0.0f and an appropriate value - will be guesed at. font_metrics - [out] font metrics in font design units Returns: @@ -206,7 +203,7 @@ Returns: 0: failed */ ON_DECL -void ON_AppleNSFontGetFontMetrics( +void ON_AppleFontGetFontMetrics( const ON_Font* font, ON_FontMetrics& font_metrics ); @@ -221,7 +218,7 @@ Returns: 0: failed */ ON_DECL -ON__UINT_PTR ON_AppleNSFontGetGlyphMetrics( +unsigned int ON_AppleFontGetGlyphMetrics( const ON_FontGlyph* glyph, ON_TextBox& glyph_metrics ); @@ -229,17 +226,14 @@ ON__UINT_PTR ON_AppleNSFontGetGlyphMetrics( /* Parameters: glyph - [in] - font_design_units_per_M - [in] - Pass 0 if not know, but try hard to pass in the correct value. figure_type - [in] Pass ON_OutlineFigure::Type::Unset if not known. outline - [out] outline and metrics in font design units */ ON_DECL -bool ON_AppleNSFontGetGlyphOutline( +bool ON_AppleFontGetGlyphOutline( const ON_FontGlyph* glyph, - unsigned int font_design_units_per_M, ON_OutlineFigure::Type figure_type, class ON_Outline& outline ); diff --git a/opennurbs_locale.cpp b/opennurbs_locale.cpp index 62fef512..f148935d 100644 --- a/opennurbs_locale.cpp +++ b/opennurbs_locale.cpp @@ -1074,7 +1074,7 @@ ON_Locale ON_Locale::FromWindowsName( ON_String cleaned_loc = language_subtag; cleaned_loc += '-'; cleaned_loc +=region_subtag; - if ( nullptr != windows_sortorder && 0 != windows_sortorder[0]) + if ( 0 != windows_sortorder[0]) { cleaned_loc += '_'; cleaned_loc += windows_sortorder; diff --git a/opennurbs_math.cpp b/opennurbs_math.cpp index 42372458..006dce32 100644 --- a/opennurbs_math.cpp +++ b/opennurbs_math.cpp @@ -1728,7 +1728,8 @@ ON_ComparePoint( // returns const double wB = (is_rat && pointB[dim] != 0.0) ? 1.0/pointB[dim] : 1.0; double a, b, tol; int i; - for ( i = 0; i < dim; i++ ) { + for ( i = 0; i < dim; i++ ) + { a = wA* *pointA++; b = wB* *pointB++; tol = (fabs(a) + fabs(b))* ON_RELATIVE_TOLERANCE; @@ -1738,11 +1739,11 @@ ON_ComparePoint( // returns return -1; if ( b < a-tol ) return 1; - if ( wA < wB- ON_SQRT_EPSILON ) - return -1; - if ( wB < wA- ON_SQRT_EPSILON ) - return -1; } + if ( wA < wB- ON_SQRT_EPSILON ) + return -1; + if ( wB < wA- ON_SQRT_EPSILON ) + return -1; return 0; } @@ -1762,31 +1763,28 @@ ON_ComparePointList( // returns ) { int i, rc = 0, rc1 = 0; - bool bDoSecondCheck = ( 1 == is_rat && dim <= 3 && point_count > 0 + const bool bDoSecondCheck = ( 1 == is_rat && dim <= 3 && point_count > 0 && ON_IsValid(pointA[dim]) && 0.0 != pointA[dim] && ON_IsValid(pointB[dim]) && 0.0 != pointB[dim] ); - const double wA = bDoSecondCheck ? pointA[dim] : 1.0; - const double wB = bDoSecondCheck ? pointB[dim] : 1.0; - const double wAtol = wA*ON_ZERO_TOLERANCE; - const double wBtol = wB*ON_ZERO_TOLERANCE; double A[3] = {0.0,0.0,0.0}; double B[3] = {0.0,0.0,0.0}; - const size_t AB_size = dim*sizeof(A[0]); for ( i = 0; i < point_count && !rc; i++ ) { rc = ON_ComparePoint( dim, is_rat, pointA, pointB ); if ( rc && bDoSecondCheck - && fabs(wA - pointA[dim]) <= wAtol - && fabs(wB - pointB[dim]) <= wBtol ) + && 0.0 != pointA[dim] && 0.0 != pointB[dim] + ) { if ( !rc1 ) rc1 = rc; - memcpy(A,pointA,AB_size); - A[0] /= pointA[dim]; A[1] /= pointA[dim]; A[2] /= pointA[dim]; - memcpy(B,pointB,AB_size); - B[0] /= pointB[dim]; B[1] /= pointB[dim]; B[2] /= pointB[dim]; + // bDoSecondCheck = true ensures is_rat is true and pointX[dim] != 0.0 + for(int k = 0; k < dim; k++) + { + A[k] = pointA[k]/pointA[dim]; + B[k] = pointB[k]/pointB[dim]; + } rc = ( 0 == ON_ComparePoint( dim, 0, A, B ) ) ? 0 : rc1; } pointA += point_strideA; diff --git a/opennurbs_mesh.cpp b/opennurbs_mesh.cpp index f6ff8f4f..eefb0a29 100644 --- a/opennurbs_mesh.cpp +++ b/opennurbs_mesh.cpp @@ -630,6 +630,7 @@ void ON_Mesh::Destroy() DestroyRuntimeCache( true ); m_Ttag.Default(); m_Ctag.Default(); + m_dV.Destroy(); m_V.Destroy(); m_F.Destroy(); m_N.Destroy(); @@ -649,6 +650,7 @@ void ON_Mesh::Destroy() void ON_Mesh::EmergencyDestroy() { DestroyRuntimeCache( false ); + m_dV.EmergencyDestroy(); m_V.EmergencyDestroy(); m_F.EmergencyDestroy(); m_N.EmergencyDestroy(); @@ -658,6 +660,8 @@ void ON_Mesh::EmergencyDestroy() m_S.EmergencyDestroy(); m_K.EmergencyDestroy(); m_C.EmergencyDestroy(); + m_NgonMap.EmergencyDestroy(); + m_Ngon.EmergencyDestroy(); } static bool ON_MeshIsNotValid(bool bSilentError) @@ -896,6 +900,123 @@ unsigned int ON_MeshNgon::IsValid( return bdry_edge_count; } +static void Internal_ON_Mesh_IsCorruptMessage( + bool bSilentError, + bool& bIsCorrupt, + ON_TextLog* text_log, + const wchar_t* corruption_description +) +{ + if (false == bIsCorrupt) + { + if (false == bSilentError) + { + ON_ERROR("ON_Mesh data is corrupt."); + } + bIsCorrupt = true; + if (nullptr != text_log) + text_log->PrintString(corruption_description); + } +} + +bool ON_Mesh::IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log +) const +{ + const unsigned int V_count = m_V.UnsignedCount(); + unsigned int F_count = m_F.UnsignedCount(); + + bool bIsCorrupt = false; + + // test faces first + for (unsigned int fi = 0; fi < F_count; fi++) + { + unsigned int* fvi = (unsigned int*)m_F[fi].vi; + for (int j = 0; j < 4; j++) + { + if (fvi[j] >= V_count) + { + Internal_ON_Mesh_IsCorruptMessage( + bSilentError, + bIsCorrupt, + text_log, + L"ON_Mesh.m_F[] has out of range vertex indices.\n" + ); + if (bRepair) + { + fvi[0] = V_count; // mark this corrupt face for deletion below + + // deleting a face makes cached information invalid + // and the fact the face was corrupt makes any + // set values suspect. + const_cast(this)->m_invalid_count = 0; + const_cast(this)->m_quad_count = 0; + const_cast(this)->m_triangle_count = 0; + + const_cast(this)->m_mesh_is_closed = 0; + const_cast(this)->m_mesh_is_manifold = 0; + const_cast(this)->m_mesh_is_oriented = 0; + const_cast(this)->m_mesh_is_solid = 0; + } + } + } + } + + if (bIsCorrupt && bRepair) + { + // remove corrupt faces + // N-gon indices reference faces that will get new indices + // or get deleted. + // If the creator couldn't get the vertex indices in a + // face set correctly, then they loose any N-gon work + // too that may or may not have been done correctly. + // (Harsh, I know). + const_cast(this)->RemoveAllNgons(); + + + unsigned int new_F_count = 0; + ON_MeshFace* F = const_cast(m_F.Array()); + ON_3fVector* FN = + (F_count == m_FN.UnsignedCount()) + ? const_cast(m_FN.Array()) + : nullptr; + if ( nullptr == FN) + const_cast(this)->m_F.SetCount(0); + + for (unsigned int fi = 0; fi < F_count; fi++) + { + if (V_count ==(unsigned int)F[fi].vi[0]) + continue; // corrupt face + + F[new_F_count] = F[fi]; + if (nullptr != FN) + FN[new_F_count] = FN[fi]; + new_F_count++; + } + + const_cast(this)->m_F.SetCount(new_F_count); + if (nullptr != FN) + const_cast(this)->m_FN.SetCount(new_F_count); + F_count = new_F_count; + } + + if (0 != m_dV.UnsignedCount() && V_count != m_dV.UnsignedCount()) + { + Internal_ON_Mesh_IsCorruptMessage( + bSilentError, + bIsCorrupt, + text_log, + L"ON_Mesh.m_dV[] has wrong size.\n" + ); + if( bRepair ) + const_cast(this)->m_dV.SetCount(0); + } + + return bIsCorrupt; +} + unsigned int ON_MeshNgon::IsValid( const ON_MeshNgon* ngon, unsigned int ngon_index, @@ -918,6 +1039,9 @@ bool ON_Mesh::IsValid( ON_TextLog* text_logx ) const bool bSilentError = ( 0 != (lowbit & ((ON__INT_PTR)text_logx)) ); ON_TextLog* text_log = (ON_TextLog*)(((ON__INT_PTR)text_logx) & hightbits); + if (IsCorrupt(false, bSilentError, text_log)) + return false; + const unsigned int facet_count = FaceUnsignedCount(); const unsigned int vertex_count = VertexUnsignedCount(); unsigned int fi, vi; @@ -2569,16 +2693,26 @@ bool ON_Mesh::Transform( const ON_Xform& xform ) { - const unsigned int vertex_count = VertexUnsignedCount(); - const bool bIsValid_fV = (vertex_count == m_V.UnsignedCount()); - const bool bIsValid_dV = (vertex_count == m_dV.UnsignedCount()); - const bool bSyncheddV = bIsValid_fV && bIsValid_dV && HasSynchronizedDoubleAndSinglePrecisionVertices(); + // Tansform user data before doing sanity checks in case rogue code + // damages m_V[], m_dV[] or other data members. TransformUserData(xform); DestroyTree(); - if ( bIsValid_fV ) - ON_TransformPointList( 3, false, vertex_count, 3, &m_dV[0][0], xform ); + const unsigned int vertex_count = VertexUnsignedCount(); + const bool bIsValid_fV = (vertex_count == m_V.UnsignedCount()); + if (false == bIsValid_fV) + m_V.SetCount(0); + + const bool bIsValid_dV = (vertex_count == m_dV.UnsignedCount()); + if (false == bIsValid_dV) + m_dV.SetCount(0); + + const bool bSyncheddV = bIsValid_fV && bIsValid_dV && HasSynchronizedDoubleAndSinglePrecisionVertices(); + + if (bIsValid_dV) + ON_TransformPointList(3, false, vertex_count, 3, &m_dV[0][0], xform); + double d = xform.Determinant(); bool rc = false; if ( bSyncheddV ) @@ -2588,7 +2722,7 @@ bool ON_Mesh::Transform( UpdateSinglePrecisionVertices(); rc = true; } - else + else if ( bIsValid_fV ) { rc = ON_TransformPointList( 3, false, vertex_count, 3, &m_V[0][0], xform ); } diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h index f5036a32..be08e29b 100644 --- a/opennurbs_mesh.h +++ b/opennurbs_mesh.h @@ -2274,6 +2274,28 @@ public: void DestroyTree( bool bDeleteTree = true ); + /* + Description: + Check for corrupt data values that are likely to cause crashes. + Parameters: + bRepair - [in] + If true, const_cast<> will be used to change the corrupt data + so that crashes are less likely. + bSilentError - [in] + If true, ON_ERROR will not be called when corruption is detected. + text_log - [out] + If text_log is not null, then a description of corruption + is printed using text_log. + Remarks: + Ideally, IsCorrupt() would be a virtual function on ON_Object, + but doing that at this point would break the public SDK. + */ + bool IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log + ) const; + ///////////////////////////////////////////////////////////////// // ON_Object overrides @@ -4088,8 +4110,8 @@ public: // If ON_Mesh::HasNgons() is true, then the mesh has n-gons. // When a mesh has ngons, m_NgonMap[] is used to determine when // a face belongs to an n-gon. - // If m_NgonMap[fi] >= 0, then it is the index of the ngon m_F[] - // belongs to. Otherwise, m_Fngon[fi] is -1. + // If m_NgonMap[fi] < m_Ngon.UnsignedCount(), then it is the index of the N-gon in m_Ngon[] + // that m_F[] belongs to. Otherwise, m_NgonMap[fi] is ON_UNSET_UINT_INDEX. ON_SimpleArray m_NgonMap; // invalid if m_NgonMap.Count() != m_F.Count() ON_SimpleArray m_Ngon; ON_MeshNgonAllocator m_NgonAllocator; // use this to allocate elements added to m_Ngon; diff --git a/opennurbs_model_component.cpp b/opennurbs_model_component.cpp index 657314a8..ae6d1738 100644 --- a/opennurbs_model_component.cpp +++ b/opennurbs_model_component.cpp @@ -3819,13 +3819,64 @@ bool ON_BinaryArchive::AddManifestMapItem( return true; } - ////////////////////////////////////////////////////////////////////////////////////////////// // // // -const ON_ModelComponentReference ON_ModelComponentReference::Empty; +// Explicit implementation to insure m_sp is completely managed in the openurbs DLL. +ON_ModelComponentWeakReference::ON_ModelComponentWeakReference() ON_NOEXCEPT +{} + +// Explicit implementation to insure m_sp is completely managed in the openurbs DLL. +ON_ModelComponentWeakReference::~ON_ModelComponentWeakReference() +{} + +// Explicit implementation to insure m_sp is completely managed in the openurbs DLL. +ON_ModelComponentWeakReference::ON_ModelComponentWeakReference(const ON_ModelComponentWeakReference& src) ON_NOEXCEPT + : m_wp(src.m_wp) +{} + +// Explicit implementation to insure m_sp is completely managed in the openurbs DLL. +ON_ModelComponentWeakReference& ON_ModelComponentWeakReference::operator=(const ON_ModelComponentWeakReference& src) +{ + if ( this != &src) + m_wp = src.m_wp; + return *this; +} + +#if defined(ON_HAS_RVALUEREF) +ON_ModelComponentWeakReference::ON_ModelComponentWeakReference( ON_ModelComponentWeakReference&& src ) ON_NOEXCEPT + : m_wp(std::move(src.m_wp)) +{} + +ON_ModelComponentWeakReference& ON_ModelComponentWeakReference::operator=(ON_ModelComponentWeakReference&& src) +{ + if ( this != &src ) + { + m_wp.reset(); + m_wp = std::move(src.m_wp); + } + return *this; +} + +#endif + +ON_ModelComponentWeakReference::ON_ModelComponentWeakReference(const ON_ModelComponentReference& src) ON_NOEXCEPT + : m_wp(src.m_sp) +{} + +ON_ModelComponentWeakReference& ON_ModelComponentWeakReference::operator=(const ON_ModelComponentReference& src) +{ + m_wp = src.m_sp; + return *this; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// +// +// // Explicit implementation to insure m_sp is completely managed in the openurbs DLL. ON_ModelComponentReference::ON_ModelComponentReference() ON_NOEXCEPT @@ -3865,6 +3916,22 @@ ON_ModelComponentReference& ON_ModelComponentReference::operator=(ON_ModelCompon #endif +ON_ModelComponentReference::ON_ModelComponentReference(const ON_ModelComponentWeakReference& src) ON_NOEXCEPT + : m_sp(src.m_wp.lock()) +{ + // NOTE WELL: + // std::shared_ptr(std::weak_ptr) throws an exception when weak_ptr is empty. + // std::shared_ptr(std::weak_ptr.lock()) constructs and empty shared_ptr when weak_ptr is empty. +} + + +ON_ModelComponentReference& ON_ModelComponentReference::operator=(const ON_ModelComponentWeakReference& src) +{ + m_sp = src.m_wp.lock(); + return *this; +} + + ON_ModelComponentReference::ON_ModelComponentReference( std::shared_ptr& sp ) ON_NOEXCEPT @@ -3919,6 +3986,14 @@ const class ON_ModelComponent* ON_ModelComponentReference::ModelComponent() cons return m_sp.get(); } +class ON_ModelComponent* ON_ModelComponentReference::ExclusiveModelComponent() const ON_NOEXCEPT +{ + return + (1 == m_sp.use_count()) + ? m_sp.get() + : nullptr; +} + ON__UINT64 ON_ModelComponentReference::ModelComponentRuntimeSerialNumber() const ON_NOEXCEPT { const ON_ModelComponent* model_component = m_sp.get(); diff --git a/opennurbs_model_component.h b/opennurbs_model_component.h index 9404c8e2..db3b12f1 100644 --- a/opennurbs_model_component.h +++ b/opennurbs_model_component.h @@ -1683,6 +1683,11 @@ private: ON_ModelComponent::Type m_types[32]; }; +/// +/// ON_ModelComponentReference is a persistent reference to a model component. +/// ON_ModelComponentReference and ON_ModelComponentWeakReference are based on +/// like std::shared_ptr and std::weak_ptr. +/// class ON_CLASS ON_ModelComponentReference { public: @@ -1699,6 +1704,13 @@ public: ON_ModelComponentReference& operator=( ON_ModelComponentReference&& ); #endif + ON_ModelComponentReference( + const class ON_ModelComponentWeakReference& weak_ref + ) ON_NOEXCEPT; + + ON_ModelComponentReference& operator=( const ON_ModelComponentWeakReference& ); + + ON_ModelComponentReference( std::shared_ptr& sp ) ON_NOEXCEPT; @@ -1743,9 +1755,33 @@ public: /* Return: A pointer to the managed model component or nullptr. + See Also: + ON_ModelComponentRef::ExclusiveModelComponent(); */ const class ON_ModelComponent* ModelComponent() const ON_NOEXCEPT; + /* + Return: + If this is the only referance to a component, the a pointer to + that component is returned. + Otherwise, nullptr is returned + See Also: + ON_ModelGeometryComponent::Attributes() + ON_ModelGeometryComponent::Geometry() + ON_ModelGeometryComponent::ExclusiveAttributes() + ON_ModelGeometryComponent::ExclusiveGeometry(); + ON_ModelComponentRef::ExclusiveModelComponent(); + ONX_Model::ComponentFromRuntimeSerialNumber() + Remarks: + If .NET or other wrappers using "lazy garbage collection" memory management are in use, + there may be stale references awaiting gargage collection and this function will return + nullptr when you think it should not. + For this function to work reliably, the ONX_Model and its components + and references should be in well constructed C++ code with carefully + crafted memory management. + */ + class ON_ModelComponent* ExclusiveModelComponent() const ON_NOEXCEPT; + ON__UINT64 ModelComponentRuntimeSerialNumber() const ON_NOEXCEPT; const ON_UUID ModelComponentId() const ON_NOEXCEPT; const ON_NameHash ModelComponentNameHash() const ON_NOEXCEPT; @@ -1760,6 +1796,8 @@ public: ) const; private: + friend class ON_ModelComponentWeakReference; + #pragma ON_PRAGMA_WARNING_PUSH #pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) // C4251: ... needs to have dll-interface to be used by clients of class ... @@ -1772,4 +1810,43 @@ private: ON_DLL_TEMPLATE template class ON_CLASS ON_ClassArray; #endif +/// +/// ON_ModelComponentWeakReference is a weak shared reference to a model component. +/// ON_ModelComponentReference and ON_ModelComponentWeakReference are based on +/// like std::shared_ptr and std::weak_ptr. +/// +class ON_CLASS ON_ModelComponentWeakReference +{ +public: + static const ON_ModelComponentWeakReference Empty; + +public: + // No = default to insure m_sp is completely managed in the openurbs DLL. + ON_ModelComponentWeakReference() ON_NOEXCEPT; // No = default to insure m_sp is completely managed in the openurbs DLL. + ~ON_ModelComponentWeakReference(); // No = default to insure m_sp is completely managed in the openurbs DLL. + ON_ModelComponentWeakReference(const ON_ModelComponentWeakReference&) ON_NOEXCEPT; // No = default to insure m_sp is completely managed in the openurbs DLL. + ON_ModelComponentWeakReference& operator=(const ON_ModelComponentWeakReference&); // No = default to insure m_sp is completely managed in the openurbs DLL. + +#if defined(ON_HAS_RVALUEREF) + ON_ModelComponentWeakReference( ON_ModelComponentWeakReference&& ) ON_NOEXCEPT; + ON_ModelComponentWeakReference& operator=( ON_ModelComponentWeakReference&& ); +#endif + + ON_ModelComponentWeakReference( + const class ON_ModelComponentReference& + ) ON_NOEXCEPT; + + ON_ModelComponentWeakReference& operator=( const ON_ModelComponentReference& ); + + +private: + friend class ON_ModelComponentReference; +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... needs to have dll-interface to be used by clients of class ... + // m_weak_sp is private and all code that manages m_weak_sp is explicitly implemented in the DLL. + std::weak_ptr m_wp; +#pragma ON_PRAGMA_WARNING_POP +}; + #endif diff --git a/opennurbs_model_geometry.cpp b/opennurbs_model_geometry.cpp index 12333a40..deb09b04 100644 --- a/opennurbs_model_geometry.cpp +++ b/opennurbs_model_geometry.cpp @@ -292,6 +292,22 @@ const ON_3dmObjectAttributes* ON_ModelGeometryComponent::Attributes( return (nullptr != ptr) ? ptr : no_attributes_return_value; } +ON_Geometry* ON_ModelGeometryComponent::ExclusiveGeometry() const +{ + return + (1 == m_geometry_sp.use_count()) + ? m_geometry_sp.get() + : nullptr; +} + +ON_3dmObjectAttributes* ON_ModelGeometryComponent::ExclusiveAttributes() const +{ + return + (1 == m_attributes_sp.use_count()) + ? m_attributes_sp.get() + : nullptr; +} + void ON_ModelGeometryComponent::Dump( ON_TextLog& text_log ) const { ON_ModelComponent::Dump(text_log); diff --git a/opennurbs_model_geometry.h b/opennurbs_model_geometry.h index ec2f0c87..fe856488 100644 --- a/opennurbs_model_geometry.h +++ b/opennurbs_model_geometry.h @@ -136,6 +136,9 @@ public: ); /* + Description: + Get a pointer to geometry. The returned pointer may be shared + and should not be used to modify the geometry. Parameters: no_geometry_return_value - [in] This value is returned if no geometric object has been set. @@ -148,12 +151,42 @@ public: If the geometry is set and something besides light, then ComponentType() will return ON_ModelComponent::Type::ModelGeometry. Otherwise, ComponentType() will return ON_ModelComponent::Type::ModelGeometry::Unset. + See Also: + ON_ModelGeometryComponent::Attributes() + ON_ModelGeometryComponent::Geometry() + ON_ModelGeometryComponent::ExclusiveAttributes() + ON_ModelGeometryComponent::ExclusiveGeometry(); + ON_ModelComponentRef::ExclusiveModelComponent(); + ONX_Model::ComponentFromRuntimeSerialNumber() */ const class ON_Geometry* Geometry( const class ON_Geometry* no_geometry_return_value ) const; /* + Description: + Get a pointer to geometry that can be used to modify the geometry. + The returned pointer is not shared at the time it is returned + and will not be shared until a copy of this ON_ModelGeometryComponent + is created. + Returns: + If this ON_ModelGeometryComponent is the only reference to the geometry, + then a pointer to the geometry is returned. + Otherwise, nullptr is returned. + See Also: + ON_ModelGeometryComponent::Attributes() + ON_ModelGeometryComponent::Geometry() + ON_ModelGeometryComponent::ExclusiveAttributes() + ON_ModelGeometryComponent::ExclusiveGeometry(); + ON_ModelComponentRef::ExclusiveModelComponent(); + ONX_Model::ComponentFromRuntimeSerialNumber() + */ + class ON_Geometry* ExclusiveGeometry() const; + + /* + Description: + Get a pointer to attributes. The returned pointer may be shared + and should not be used to modify the attributes. Parameters: no_attributes_return_value - [in] This value is returned if no attributes have been set. @@ -163,11 +196,38 @@ public: Returns: The layer, rendering and other attributes for this element, or no_attributes_return_value if the attributes have not been set. + See Also: + ON_ModelGeometryComponent::Attributes() + ON_ModelGeometryComponent::Geometry() + ON_ModelGeometryComponent::ExclusiveAttributes() + ON_ModelGeometryComponent::ExclusiveGeometry(); + ON_ModelComponentRef::ExclusiveModelComponent(); + ONX_Model::ComponentFromRuntimeSerialNumber() */ const ON_3dmObjectAttributes* Attributes( const ON_3dmObjectAttributes* no_attributes_return_value ) const; + /* + Description: + Get a pointer to attributes that can be used to modify the attributes. + The returned pointer is not shared at the time it is returned + and will not be shared until a copy of this ON_ModelGeometryComponent + is created. + Returns: + If this ON_ModelGeometryComponent is the only reference to the attributes, + then a pointer to the attributes is returned. + Otherwise, nullptr is returned. + See Also: + ON_ModelGeometryComponent::Attributes() + ON_ModelGeometryComponent::Geometry() + ON_ModelGeometryComponent::ExclusiveAttributes() + ON_ModelGeometryComponent::ExclusiveGeometry(); + ON_ModelComponentRef::ExclusiveModelComponent(); + ONX_Model::ComponentFromRuntimeSerialNumber() + */ + class ON_3dmObjectAttributes* ExclusiveAttributes() const; + private: #pragma ON_PRAGMA_WARNING_PUSH diff --git a/opennurbs_nurbscurve.cpp b/opennurbs_nurbscurve.cpp index 61b453e2..194d35d0 100644 --- a/opennurbs_nurbscurve.cpp +++ b/opennurbs_nurbscurve.cpp @@ -3114,6 +3114,403 @@ bool ON_NurbsCurve::ClampEnd( } +double ON_NurbsCurve::GetCubicBezierApproximation( + double max_deviation, + class ON_BezierCurve& bezierCurve +) const +{ + ON_3dPoint bezCV[4]; + const double deviation = GetCubicBezierApproximation(max_deviation, bezCV); + if (deviation >= 0.0) + { + bezierCurve.Create(3, false, 4); + bezierCurve.SetCV(0, bezCV[0]); + bezierCurve.SetCV(1, bezCV[1]); + bezierCurve.SetCV(2, bezCV[2]); + bezierCurve.SetCV(3, bezCV[3]); + } + return deviation; +} + +double ON_NurbsCurve::GetCubicBezierApproximation( + double max_deviation, + ON_3dPoint bezCV[4] +) const +{ + const double failed_rc = ON_DBL_QNAN; + + if (nullptr == this) + return failed_rc; + if (m_order < 2) + return failed_rc; + if (m_cv_count < m_order) + return failed_rc; + if ( nullptr == bezCV) + return failed_rc; + + if ( + 0 == m_is_rat + && 4 == m_order + && 4 == m_cv_count + && m_knot[0] == m_knot[2] + && m_knot[3] == m_knot[5] + ) + { + GetCV(0, bezCV[0]); + GetCV(1, bezCV[1]); + GetCV(2, bezCV[2]); + GetCV(3, bezCV[3]); + return 0.0; + } + + const ON_Interval domain = Domain(); + + + // bez0CV[] are cubic bezier control points set from end locations and interpolating 2 interior points. + ON_3dPoint bez0CV[4] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; + // bez1CV[] are cubic bezier control points set from end locations and derivatives. + ON_3dPoint bez1CV[4] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; + + // bez1CV[] are cubic bezier control points set from end locations and derivatives. + Evaluate(domain[0], 1, 3, &bez1CV[0].x, 0, nullptr); + Evaluate(domain[1], 1, 3, &bez1CV[2].x, 0, nullptr); + bez0CV[0] = bez1CV[0]; + bez0CV[3] = bez1CV[2]; + const double c = domain[1] - domain[0]; + const ON_3dVector D[2]{ c * bez1CV[1], c * bez1CV[3] }; + bez1CV[1] = bez0CV[0] + D[0]/3.0; + bez1CV[2] = bez0CV[3] - D[1]/3.0; + bez1CV[3] = bez0CV[3]; + + // bez0CV[] are cubic bezier control points set from end locations and interpolating 2 interior points. + ON_3dPoint P[2] = {ON_3dPoint::Origin,ON_3dPoint::Origin}; + int hint = 0; + Evaluate(domain.ParameterAt(1.0/3.0), 0, 3, &P[0].x, 0, &hint); + Evaluate(domain.ParameterAt(2.0/3.0), 0, 3, &P[1].x, 0, &hint); + bez0CV[1] = (bez0CV[3] - 2.5*bez0CV[0])/3.0 + (3.0*P[0] - 1.5*P[1]); + bez0CV[2] = (bez0CV[0] - 2.5*bez0CV[3])/3.0 + (3.0*P[1] - 1.5*P[0]); + + double zero_tol = 0.0; + double deviation0 = 0.0; + double deviation1 = 0.0; + if (4 != m_order || 4 != m_cv_count || 0 != m_is_rat) + { + // g[] = parameters for evaluation test + double g32[32]; + double* g = (m_cv_count <= 32) ? g32 : (double*)onmalloc(m_cv_count * sizeof(g[0])); + GetGrevilleAbcissae(g); + if (3 == m_order) + { + const double* k = m_knot; + if (k[0] < k[2]) + { + g[0] = k[2]; + g[1] = (2.0*k[2] + k[3]) / 3.0; + } + k += (m_cv_count - m_order); + if (k[3] < k[5]) + { + g[m_cv_count - 1] = k[3]; + g[m_cv_count - 2] = (2.0*k[3] + k[2]) / 3.0; + } + } + + // test evaluation + ON_BezierCurve bez; + bez.m_dim = 3; + bez.m_is_rat = 0; + bez.m_order = 4; + bez.m_cv_stride = 3; + bez.m_cv = &bez0CV[0].x; + + ON_3dPoint A, B; + bez.Evaluate(1.0 / 3.0, 0, 3, &A.x); + bez.Evaluate(2.0 / 3.0, 0, 3, &B.x); + zero_tol = 16.0*(P[0].DistanceTo(A) + P[1].DistanceTo(B)) + 1.0e-14; + double d; + for (int i = 1; i < m_cv_count; i++) + { + for (int pass = 0; pass < 2; pass++) + { + const double t = (0==pass) ? 0.5*(g[i - 1] + g[i]) : g[i]; + const double s = domain.NormalizedParameterAt(t); + if (false == (s > 0.0 && s < 1.0)) + continue; + + Evaluate(t, 0, 3, &A.x, 0, &hint); + + // test bez0CV[] + bez.m_cv = &bez0CV[0].x; + bez.Evaluate(s, 0, 3, &B.x); + d = A.DistanceTo(B); + if (d > deviation0) + deviation0 = d; + + // test bez1CV[] + bez.m_cv = &bez1CV[0].x; + bez.Evaluate(s, 0, 3, &B.x); + d = A.DistanceTo(B); + if (d > deviation1) + deviation1 = d; + + if (max_deviation > zero_tol && deviation0 > max_deviation && deviation1 > max_deviation) + return failed_rc; + } + } + + bez.m_cv = nullptr; + + if (g != g32) + onfree(g); + } + + + if (deviation1 <= zero_tol || deviation1 <= deviation0) + { + // use cubic bezier CVs calculated from end locations and interpolating end derivatives. + bezCV[0] = bez1CV[0]; + bezCV[1] = bez1CV[1]; + bezCV[2] = bez1CV[2]; + bezCV[3] = bez1CV[3]; + return (deviation1 > zero_tol) ? deviation1 : 0.0; + } + + // use cubic bezier CVs calculated from end locations and interpolating 2 interior points. + bezCV[0] = bez0CV[0]; + bezCV[1] = bez0CV[1]; + bezCV[2] = bez0CV[2]; + bezCV[3] = bez0CV[3]; + return (deviation0 > zero_tol) ? deviation0 : 0.0; +} + +double ON_NurbsSurface::GetCubicBezierApproximation( + double max_deviation, + class ON_BezierSurface& bezierSurface +) const +{ + ON_3dPoint bezCV[4][4]; + const double deviation = GetCubicBezierApproximation(max_deviation, bezCV); + if (deviation >= 0.0) + { + bezierSurface.Create(3, false, 4, 4); + for ( int i = 0; i < 4; i++) for ( int j = 0; j < 4; j++) + bezierSurface.SetCV(i, j, bezCV[i][j]); + } + return deviation; +} + +double ON_NurbsSurface::GetCubicBezierApproximation( + double max_deviation, + ON_3dPoint bezCV[4][4] +) const +{ + const double failed_rc = ON_DBL_QNAN; + + if (nullptr == this) + return failed_rc; + + if (nullptr == this) + return failed_rc; + if (this->m_order[0] < 2 || this->m_order[1] < 2) + return failed_rc; + if (this->m_cv_count[0] < this->m_order[0] || this->m_cv_count[0] > 64) + return failed_rc; + if (this->m_cv_count[1] < this->m_order[1] || this->m_cv_count[1] > 64) + return failed_rc; + + ON_3dPoint Q[4]; + + const ON_Interval domain[2] = { this->Domain(0),this->Domain(1) }; + + const double c[4] = { domain[1][0],domain[0][1],domain[1][1],domain[0][0] }; + const double maximum_deviation = ON_DBL_QNAN; + double deviation = 0.0; + for (int side = 0; side < 4; side++) + { + ON_Curve* iso = this->IsoCurve((side%2), c[side]); + if (nullptr == iso) + return failed_rc; + const ON_NurbsCurve* nurbs_curve = ON_NurbsCurve::Cast(iso); + double d + = (nullptr != nurbs_curve) + ? nurbs_curve->GetCubicBezierApproximation( + maximum_deviation, + (0 == (side % 2)) ? Q : &bezCV[(1 == side) ? 3 : 0][0]) + : ON_DBL_QNAN; + if (false == (d >= 0.0)) + return failed_rc; + if (d > deviation) + deviation = d; + if (0 == (side % 2)) + { + int j = (0 == side) ? 0 : 3; + bezCV[1][j] = Q[1]; + bezCV[2][j] = Q[2]; + } + delete iso; + } + + bezCV[1][1] = ON_3dPoint::Origin; + bezCV[1][2] = ON_3dPoint::Origin; + bezCV[2][1] = ON_3dPoint::Origin; + bezCV[2][2] = ON_3dPoint::Origin; + + ON_BezierSurface bezS; + bezS.m_dim = 3; + bezS.m_is_rat = 0; + bezS.m_order[0] = 4; + bezS.m_order[1] = 4; + bezS.m_cv_stride[0] = (int)(&bezCV[1][0].x - &bezCV[0][0].x); + bezS.m_cv_stride[1] = (int)(&bezCV[0][1].x - &bezCV[0][0].x); + bezS.m_cv = &bezCV[0][0].x; + + + // b[0][] = 27*(bernstein cubics at 1/3) + // b[1][] = 27*(bernstein cubics at 2/3) + const double b[2][4] = { + { 8.0, 12.0, 6.0, 1.0 }, + { 1.0, 6.0, 12.0, 8.0 } + }; + + // uv[4] = {{1/3,1/3}, {2/3,1/3}, {1/3,2/3}, {2/3,2/3} } + // K[n] = (27*27)*bezCV(uv[n]) + // P[n] = this(domain.ParamaterAt(uv[n])); + // 36* M * Transpose[C11,C21,C12,C22] = [X0,X1,X2,X3] + // uv[4] = {{1/3,1/3{, {2/3,1/3}, {1/3,2/3}, {2/3,2/3} } + // X0 = nurbs_srf(1/3,1/3) - K[0]/(27*27) + // A[4][4] = 4x4 matrix with rows + // 36*A[0][] = {b1[1]*b1[1], b1[2]*b1[1], b1[1]*b1[2], b1[2]*b1[2] } + // 36*A[1][] = {b2[1]*b1[1], b2[2]*b1[1], b2[1]*b1[2], b2[2]*b1[2] } + // 36*A[2][] = {b1[1]*b2[1], b1[2]*b2[1], b1[1]*b2[2], b1[2]*b2[2] } + // 36*A[3][] = {b2[1]*b2[1], b2[2]*b2[1], b2[1]*b2[2], b2[2]*b2[2] } + // K[n] + 36*(A[n][0]*C11 + A[n][1]*C21 + A[n][2]*C12 + A[n][3]*C22) = (27*27)P[n] + // A*Transpose(C11,C21,C12,C22) = Transpose(X[0],X[1],X[2],X[3]) + // X[n] = ((27*27)P[n] - K[n])/36 = 81/4*P[n] - K[n]/36.0 + // Inverse(A) = M/9. + // Transpose(C11,C21,C12,C22) = M*Transpose(X[0],X[1],X[2],X[3])/9 + // Q[n] = X[n]/9 = 2.25*P[n] - K[n]/324.0 + // bezCV[cvdex[n].i][cvdex[n].i] = M*Transpose(X[0],X[1],X[2],X[3])/9 + const double bezuv[4][2] = + { + {(1.0 / 3.0), (1.0 / 3.0)}, + {(2.0 / 3.0), (1.0 / 3.0)}, + {(1.0 / 3.0), (2.0 / 3.0)}, + {(2.0 / 3.0), (2.0 / 3.0)} + }; + ON_3dPoint srfP[4] = { ON_3dPoint::Origin,ON_3dPoint::Origin,ON_3dPoint::Origin,ON_3dPoint::Origin }; + int hint[2] = {}; + for (int n = 0; n < 4; n++) + { + this->Evaluate(domain[0].ParameterAt(bezuv[n][0]),domain[1].ParameterAt(bezuv[n][1]), 0, 3, &srfP[n].x, 0, hint); + const double* bi = b[n%2]; + const double* bj = b[n/2]; + ON_3dPoint K(0.0, 0.0, 0.0); + ON_3dPoint valQ(0, 0, 0); + for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) + { + if ((1 == i || 2 == i) && (1 == j || 2 == j)) + continue; + K += (bi[i] * bj[j])*bezCV[i][j]; + } + Q[n] = (2.25*srfP[n]) - (K/324.0); + } + + const ON_2dex cvdex[4]{ON_2dex(1,1),ON_2dex(2,1),ON_2dex(1,2),ON_2dex(2,2)}; + + ////const double A[4][4] = + ////{ + //// {4.0, 2.0, 2.0, 1.0}, + //// {2.0, 4.0, 1.0, 2.0}, + //// {2.0, 1.0, 4.0, 2.0}, + //// {1.0, 2.0, 2.0, 4.0} + ////}; + + const double M[4][4] = + { + {4.0,-2.0,-2.0,1.0}, + {-2.0,4.0,1.0,-2.0}, + {-2.0,1.0,4.0,-2.0}, + {1.0,-2.0,-2.0,4.0} + }; + + for (int n = 0; n < 4; n++) + bezCV[cvdex[n].i][cvdex[n].j] = M[n][0] * Q[0] + M[n][1] * Q[1] + M[n][2] * Q[2] + M[n][3] * Q[3]; + + double zero_tol = 0.0; + for (int n = 0; n < 4; n++) + { + ON_3dPoint bezP(0.0, 0.0, 0.0); + bezS.Evaluate(bezuv[n][0], bezuv[n][1], 0, 3, &bezP.x); + double d = srfP[n].DistanceTo(bezP); + zero_tol += d; + } + zero_tol = 16.0*zero_tol; + + double g_buffer[64]; + double* g[2]; + g[0] + = (m_cv_count[0] + m_cv_count[1] <= 32) + ? g_buffer + : (double*)onmalloc((m_cv_count[0] + m_cv_count[1]) * sizeof(g[0][0])); + g[1] = g[0] + m_cv_count[0]; + for (int dir = 0; dir < 2; dir++) + { + this->GetGrevilleAbcissae(dir, g[dir]); + if (4 == this->m_order[dir]) + { + const double* k = this->m_knot[dir]; + if (k[0] < k[2]) + { + g[dir][0] = k[2]; + g[dir][1] = (2.0*k[2] + k[3]) / 3.0; + } + const int cv_count = this->m_cv_count[dir]; + k += (cv_count - this->m_order[dir]); + if (k[3] < k[5]) + { + g[dir][cv_count-1] = k[3]; + g[dir][cv_count-2] = (2.0*k[3] + k[2]) / 3.0; + } + } + } + + ON_3dPoint bezP(ON_3dPoint::Origin), nurbsP(ON_3dPoint::Origin); + hint[0] = 0; + hint[1] = 0; + for (int i = 1; i < this->m_cv_count[0]; i++) + { + for (int passi = 0; passi < 2; passi++) + { + const double t0 = (0 == passi) ? 0.5*(g[0][i - 1] + g[0][i]) : g[0][i]; + const double s0 = domain[0].NormalizedParameterAt(t0); + if (false == (s0 > 0.0 && s0 < 1.0)) + continue; + for (int j = 1; j < this->m_cv_count[1]; j++) + { + for (int passj = 0; passj < 2; passj++) + { + const double t1 = (0 == passj) ? 0.5*(g[1][j - 1] + g[1][j]) : g[1][j]; + const double s1 = domain[1].NormalizedParameterAt(t1); + if (false == (s1 > 0.0 && s1 < 1.0)) + continue; + bezS.Evaluate(s0,s1, 0, 3, &bezP.x); + this->Evaluate(t0,t1, 0, 3, &nurbsP.x, 0, hint); + double d = nurbsP.DistanceTo(bezP); + if (d > zero_tol && d > deviation) + { + deviation = d; + if (max_deviation >= 0.0 && deviation > max_deviation) + return failed_rc; + } + } + } + } + } + + return deviation; +} + + /* static bool ON_DuplicateKnots( int order, int cv_count, bool bRevKnot1, diff --git a/opennurbs_nurbscurve.h b/opennurbs_nurbscurve.h index a82b7c5b..dc6b7a39 100644 --- a/opennurbs_nurbscurve.h +++ b/opennurbs_nurbscurve.h @@ -692,6 +692,44 @@ public: double* nurbs_t ) const override; + /* + Description: + Approximate the entire NURBS curve with a single nonrational cubic bezier curve. + Typically, the NURBS curve has only a few bispans. + Parameters: + max_deviation - [in] + If max_deviation >= 0.0, then the approximation is returned only + if the deviation sample is <= max_deviation. + bezierCurve - [out] + Returns: + ON_DBL_QNAN: no bezier curve is returned. + If a bezier curve is returned, then the maximum deviation between + the bezier curve this NURBS curve sampled at the Greville abcissa. + */ + double GetCubicBezierApproximation( + double max_deviation, + class ON_BezierCurve& bezierCurve + ) const; + + /* + Description: + Approximate the entire NURBS surface with a single nonrational cubic bezier surface. + Typically, the NURBS surface has only a few bispans. + Parameters: + max_deviation - [in] + If max_deviation >= 0.0, then the approximation is returned only + if the deviation sample is <= max_deviation. + bezierSurface - [out] + Returns: + ON_DBL_QNAN: no bezier surface is returned. + If a bezier surface is returned, then the maximum deviation between + the bezier suface this NURBS surface sampled at the Greville abcissa. + */ + double GetCubicBezierApproximation( + double max_deviation, + ON_3dPoint bezCV[4] + ) const; + public: ///////////////////////////////////////////////////////////////// diff --git a/opennurbs_nurbssurface.h b/opennurbs_nurbssurface.h index 877ab0d3..f5568daf 100644 --- a/opennurbs_nurbssurface.h +++ b/opennurbs_nurbssurface.h @@ -618,6 +618,44 @@ public: int HasNurbForm( ) const override; + /* + Description: + Approximate the entire NURBS surface with a single nonrational cubic bezier surface. + Typically, the NURBS surface has only a few bispans. + Parameters: + max_deviation - [in] + If max_deviation >= 0.0, then the approximation is returned only + if the deviation sample is <= max_deviation. + bezierSurface - [out] + Returns: + ON_DBL_QNAN: no bezier surface is returned. + If a bezier surface is returned, then the maximum deviation between + the bezier suface this NURBS surface sampled at the Greville abcissa. + */ + double GetCubicBezierApproximation( + double max_deviation, + class ON_BezierSurface& bezierSurface + ) const; + + /* + Description: + Approximate the entire NURBS surface with a single nonrational cubic bezier surface. + Typically, the NURBS surface has only a few bispans. + Parameters: + max_deviation - [in] + If max_deviation >= 0.0, then the approximation is returned only + if the deviation sample is <= max_deviation. + bezierSurface - [out] + Returns: + ON_DBL_QNAN: no bezier surface is returned. + If a bezier surface is returned, then the maximum deviation between + the bezier suface this NURBS surface sampled at the Greville abcissa. + */ + double GetCubicBezierApproximation( + double max_deviation, + ON_3dPoint bezCV[4][4] + ) const; + ///////////////////////////////////////////////////////////////// // Interface diff --git a/opennurbs_object.cpp b/opennurbs_object.cpp index 2fd715aa..3ce146cc 100644 --- a/opennurbs_object.cpp +++ b/opennurbs_object.cpp @@ -1775,6 +1775,50 @@ bool ON_Object::IsValid(ON_TextLog* text_log) const return true; } +bool ON_Object::IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log +) const +{ + bool rc = true; + if (this == nullptr) + { + if (false == bSilentError) + ON_ERROR("this is nullptr."); + } + else + { + switch (ObjectType()) + { + case ON::object_type::brep_object: + { + const ON_Brep* brep = ON_Brep::Cast(this); + if (brep) + rc = brep->ON_Brep::IsCorrupt(bRepair, bSilentError, text_log); + else if ( false == bSilentError ) + ON_ERROR("ON_Brep::Cast(this) failed."); + } + break; + + case ON::object_type::mesh_object: + { + const ON_Mesh* mesh = ON_Mesh::Cast(this); + if (mesh) + rc = mesh->ON_Mesh::IsCorrupt(bRepair, bSilentError, text_log); + else if ( false == bSilentError ) + ON_ERROR("ON_Mesh::Cast(this) failed."); + } + break; + + default: + rc = false; + break; + } + } + return rc; +} + void ON_Object::Dump( ON_TextLog& dump ) const { const ON_ClassId* p = ClassId(); diff --git a/opennurbs_object.h b/opennurbs_object.h index dce7d145..33a2e73b 100644 --- a/opennurbs_object.h +++ b/opennurbs_object.h @@ -539,6 +539,28 @@ public: */ virtual bool IsValid( class ON_TextLog* text_log = nullptr ) const; + /* + Description: + Check for corrupt data values that are likely to cause crashes. + Parameters: + bRepair - [in] + If true, const_cast<> will be used to change the corrupt data + so that crashes are less likely. + bSilentError - [in] + If true, ON_ERROR will not be called when corruption is detected. + text_log - [out] + If text_log is not null, then a description of corruption + is printed using text_log. + Remarks: + Ideally, IsCorrupt() would be a virtual function on ON_Object, + but doing that at this point would break the public SDK. + */ + bool IsCorrupt( + bool bRepair, + bool bSilentError, + class ON_TextLog* text_log + ) const; + /* Description: Creates a text dump of the object. diff --git a/opennurbs_object_history.cpp b/opennurbs_object_history.cpp index 4b4b1c20..ba138a07 100644 --- a/opennurbs_object_history.cpp +++ b/opennurbs_object_history.cpp @@ -2657,12 +2657,14 @@ void ON_CurveProxyHistory::Destroy() m_full_real_curve_domain = ON_Interval::EmptyInterval; m_sub_real_curve_domain = ON_Interval::EmptyInterval; m_proxy_curve_domain = ON_Interval::EmptyInterval; + m_segment_edge_domain = ON_Interval::EmptyInterval; + m_segment_trim_domain = ON_Interval::EmptyInterval; } bool ON_CurveProxyHistory::Write( ON_BinaryArchive& file ) const { - if ( !file.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK,1,0) ) + if ( !file.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK,1,1) ) return false; bool rc = false; @@ -2676,7 +2678,12 @@ bool ON_CurveProxyHistory::Write( ON_BinaryArchive& file ) const break; if ( !file.WriteInterval(m_sub_real_curve_domain) ) break; - if ( !file.WriteInterval(m_proxy_curve_domain) ) + if (!file.WriteInterval(m_proxy_curve_domain)) + break; + // Added in version 1,1 + if (!file.WriteInterval(m_segment_edge_domain)) + break; + if (!file.WriteInterval(m_segment_trim_domain)) break; rc = true; break; @@ -2709,9 +2716,15 @@ bool ON_CurveProxyHistory::Read( ON_BinaryArchive& file ) break; if ( !file.ReadInterval(m_sub_real_curve_domain) ) break; - if ( !file.ReadInterval(m_proxy_curve_domain) ) + if (!file.ReadInterval(m_proxy_curve_domain)) break; - + if (version_minor > 0) + { + if (!file.ReadInterval(m_segment_edge_domain)) + break; + if (!file.ReadInterval(m_segment_trim_domain)) + break; + } rc = true; break; } diff --git a/opennurbs_object_history.h b/opennurbs_object_history.h index 9fa9c8dd..3e54ef87 100644 --- a/opennurbs_object_history.h +++ b/opennurbs_object_history.h @@ -28,6 +28,8 @@ public: // reference in history records. ON_CurveProxyHistory(); ~ON_CurveProxyHistory(); + ON_CurveProxyHistory(const ON_CurveProxyHistory&) = default; + ON_CurveProxyHistory& operator=(const ON_CurveProxyHistory&) = default; ON_ObjRef m_curve_ref; // from ON_CurveProxy.m_real_curve bool m_bReversed; // from ON_CurveProxy.m_bReversed @@ -35,13 +37,17 @@ public: ON_Interval m_sub_real_curve_domain; // from ON_CurveProxy.m_real_curve_domain ON_Interval m_proxy_curve_domain; // from ON_CurveProxy.m_this_domain + // If these are empty intervals, they are from old files. Ignore them. + ON_Interval m_segment_edge_domain; + ON_Interval m_segment_trim_domain; + void Destroy(); bool Write( ON_BinaryArchive& ) const; bool Read( ON_BinaryArchive& ); void Dump( ON_TextLog& ) const; private: - ON__UINT8 m_reserved[64]; + ON__UINT8 m_reserved[32]; }; #if defined(ON_DLL_TEMPLATE) diff --git a/opennurbs_point.cpp b/opennurbs_point.cpp index 7365c50b..13bd981c 100644 --- a/opennurbs_point.cpp +++ b/opennurbs_point.cpp @@ -9152,6 +9152,371 @@ bool operator!=(const ON_4dRect& lhs, const ON_4dRect& rhs) } +int ON_WindingNumber::WindingNumber() const +{ + int winding_number = m_left_crossing_number; + if (abs(m_right_crossing_number) < abs(winding_number)) + winding_number = m_right_crossing_number; + if (abs(m_above_crossing_number) < abs(winding_number)) + winding_number = m_above_crossing_number; + if (abs(m_below_crossing_number) < abs(winding_number)) + winding_number = m_below_crossing_number; + return winding_number; +} + +const ON_2dPoint ON_WindingNumber::WindingPoint() const +{ + return m_winding_point; +} + +unsigned int ON_WindingNumber::BoundarySegmentCount() const +{ + return m_boundary_segment_count; +} + +const ON_2dPoint ON_WindingNumber::PreviousBoundaryPoint() const +{ + return m_prev_boundary_point; +} + +bool ON_WindingNumber::Internal_HaveWindingPoint() const +{ + // m_point.x is a NAN when not initialized + return (m_winding_point.x == m_winding_point.x); +} + +void ON_WindingNumber::SetWindingPoint(double x, double y) +{ + return ON_WindingNumber::SetWindingPoint(ON_2dPoint(x, y)); +} + +void ON_WindingNumber::SetWindingPoint(ON_2dPoint winding_point) +{ + *this = ON_WindingNumber::Unset; + if (winding_point.IsValid()) + m_winding_point = winding_point; +} + +//bool ON_WindingNumber::Internal_IsLeft( const double* p, const double* q ) +//{ +// // Input: p and q are 2d points with p.y <= 0 and q.y > 0 +// // +// // Returns: +// // true if the x coordinate of the intersection of the line segment from p to q and the x axis +// // is negative; i.e., the intersection point is to the left of the origin (0,0). +// // +// // Arithmetic: +// // L = segment from p to q. +// // L crosses the x-axis (y=0) +// // L(t) = (1-t)*P + t*Q; +// // When t = p.y/(p.y-q.y), L(t).y == 0 +// // (1-t) = (-q.y/(p.y-q.y)) +// // L(t).x = (-q.y/(p.y-q.y))*p.x + (p.y/(p.y-q.y))*q.x +// // = (p.y*q.x - p.x*q.y)/(p.y-q.y) +// // Since (p.y <= 0 and q.y > 0), (p.y-q.y) < 0 +// // Therefore L(t).x < 0 if and only if (p.y*q.x - p.x*q.y) > 0. +// // Therefore L(t).x < 0 if and only if p.y*q.x > p.x*q.y. +// return (p[1]*q[0]) > (p[0]*q[1]); +//} + + +int ON_WindingNumber::Internal_SignOfX(const ON_2dPoint& p, const ON_2dPoint& q) +{ + // Input: p and q are 2d points with p.y <= 0 and q.y > 0 + // + // Returns: + // Sign of the x coordinate of the intersection of the line segment from p to q + // and the x axis. + // + // Arithmetic: + // L = segment from p to q. + // L crosses the x-axis (y=0) + // L(t) = (1-t)*P + t*Q; + // When t = p.y/(p.y-q.y), L(t).y == 0 + // (1-t) = (-q.y/(p.y-q.y)) + // L(t).x = (-q.y/(p.y-q.y))*p.x + (p.y/(p.y-q.y))*q.x + // = (p.y*q.x - p.x*q.y)/(p.y-q.y) + // Since (p.y <= 0 and q.y > 0), (p.y-q.y) < 0 + // Therefore + // L(t).x < 0 if and only if (p.y*q.x - p.x*q.y) > 0. + // L(t).x > 0 if and only if (p.y*q.x - p.x*q.y) < 0. + const double x = (p.y*q.x) - (p.x*q.y); + if (x < 0.0) + return 1; + if (x > 0.0) + return -1; + return 0; +} + +int ON_WindingNumber::Internal_SignOfY(const ON_2dPoint& p, const ON_2dPoint& q) +{ + // Input: p and q are 2d points with p.x <= 0 and q.x > 0 + // + // Returns: + // Sign of the y coordinate of the intersection of the line segment from p to q + // and the y axis. + // + // Arithmetic: + // L = segment from p to q. + // L crosses the y-axis (x=0) + // L(t) = (1-t)*P + t*Q; + // When t = p.x/(p.x-q.x), L(t).x == 0 + // (1-t) = (-q.x/(p.x-q.x)) + // L(t).y = (-q.x/(p.x-q.x))*p.y + (p.x/(p.x-q.x))*q.y + // = (p.x*q.y - p.y*q.x)/(p.x-q.x) + // Since (p.x <= 0 and q.x > 0), (p.x-q.x) < 0 + // Therefore + // L(t).y < 0 if and only if (p.x*q.y - p.y*q.x) > 0. + // L(t).y > 0 if and only if (p.x*q.y - p.y*q.x) < 0. + const double y = (p.x*q.y) - (p.y*q.x); + if (y < 0.0) + return 1; + if (y > 0.0) + return -1; + return 0; +} + +void ON_WindingNumber::Internal_AddBoundarySegment(const double* p, const double* q) +{ + ///////////////////////////////////////////////////////////////////////// + // The calculation below is a modified version of a portion of wn_PnPoly() + // with a bug fix for the case when m_winding_point is on the polyline. + // + // http://geomalgorithms.com/a03-_inclusion.html + // + // Copyright 2000 softSurfer, 2012 Dan Sunday + // This code may be freely used and modified for any purpose + // providing that this copyright notice is included with it. + // SoftSurfer makes no warranty for this code, and cannot be held + // liable for any real or imagined damage resulting from its use. + // Users of this code must verify correctness for their application. + ///////////////////////////////////////////////////////////////////////// + + // calling function insures m_winding_point is set. + // In the comments below H is the horizontal line through the winding point. + const ON_2dPoint p0(p[0] - m_winding_point.x, p[1] - m_winding_point.y); + const ON_2dPoint p1(q[0] - m_winding_point.x, q[1] - m_winding_point.y); + int sign_of; + + if (p0.y <= 0.0) + { + if (p1.y > 0.0) + { + // p0.y <= 0, p1.y > 0 (line segment goes "up") and ... + sign_of = ON_WindingNumber::Internal_SignOfX(p0, p1); + if ( sign_of < 0 ) + { + // ... intersects the x-axis to the left of the origin. + --m_left_crossing_number; + m_status_bits |= 1; + } + else if (sign_of > 0) + { + // ... intersects the x-axis to the right of the origin. + ++m_right_crossing_number; + m_status_bits |= 2; + } + } + } + else + { + if (p1.y <= 0.0) + { + // p1.y <= 0 and p0.y > 0 (line segment from p0 to p1 goes "down") ... + sign_of = ON_WindingNumber::Internal_SignOfX(p1, p0); // YES, (p1, p0) is correct. + if (sign_of < 0) + { + // ... and intersects the x-axis to the left of the origin. + ++m_left_crossing_number; + m_status_bits |= 1; + } + else if (sign_of > 0) + { + // ... and intersects the x-axis to the right of the origin. + --m_right_crossing_number; + m_status_bits |= 2; + } + } + } + + + if (p0.x <= 0.0) + { + if (p1.x > 0.0) + { + // p0.x <= 0, p1.x > 0 (line segment from p0 to p1 goes "right") ... + sign_of = ON_WindingNumber::Internal_SignOfY(p0, p1); + if ( sign_of < 0 ) + { + // ... and intersects the y-axis to the below of the origin. + ++m_below_crossing_number; + m_status_bits |= 4; + } + else if (sign_of > 0) + { + // ... and intersects the y-axis to the above of the origin. + --m_above_crossing_number; + m_status_bits |= 8; + } + } + } + else + { + if (p1.x <= 0.0) + { + // p1.x <= 0 and p0.x > 0 (line segment from p0 to p1 goes "down") ... + sign_of = ON_WindingNumber::Internal_SignOfY(p1, p0); // YES, (p1, p0) is correct. + if (sign_of < 0) + { + // ... and intersects the y-axis to the left of the origin. + --m_below_crossing_number; + m_status_bits |= 4; + } + else if (sign_of > 0) + { + // ... and intersects the y-axis to the right of the origin. + ++m_above_crossing_number; + m_status_bits |= 8; + } + } + } + + if (0.0 == p0.x && 0.0 == p1.x && p0.y != p1.y ) + { + if ((p0.y <= 0.0 && p1.y >= 0.0) || (p0.y >= 0.0 && p1.y <= 0.0)) + m_status_bits |= 32; // vertical segment on winding point + } + else if (0.0 == p0.y && 0.0 == p1.y && p0.x != p1.x) + { + if ( (p0.x <= 0.0 && p1.x >= 0.0) || (p0.x >= 0.0 && p1.x <= 0.0) ) + m_status_bits |= 16; // horizontal segment on winding point + } + + m_prev_boundary_point.x = q[0]; + m_prev_boundary_point.y = q[1]; + ++m_boundary_segment_count; +} + +ON__UINT32 ON_WindingNumber::AddBoundary(ON_2dPoint p) +{ + const ON__UINT32 boundary_segment_count0 = m_boundary_segment_count; + if (Internal_HaveWindingPoint()) + { + if ( m_prev_boundary_point.x == m_prev_boundary_point.x ) + Internal_AddBoundarySegment(&m_prev_boundary_point.x, &p.x); + else + m_prev_boundary_point = p; + } + return (m_boundary_segment_count - boundary_segment_count0); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(ON_2dPoint p, ON_2dPoint q) +{ + const ON__UINT32 boundary_segment_count0 = m_boundary_segment_count; + if (Internal_HaveWindingPoint()) + { + Internal_AddBoundarySegment(&p.x, &q.x); + } + return (m_boundary_segment_count - boundary_segment_count0); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, size_t point_stride, const double* boundary_points, bool bCloseBoundary) +{ + const ON__UINT32 boundary_segment_count0 = m_boundary_segment_count; + if (Internal_HaveWindingPoint() && point_count >= 2 && point_stride >= 2 && nullptr != boundary_points) + { + const double* last_point = boundary_points + ((point_count - 1)*point_stride); + for (const double* p = boundary_points; p < last_point; p += point_stride) + { + Internal_AddBoundarySegment(p, p + point_stride); + } + if (bCloseBoundary) + { + Internal_AddBoundarySegment(last_point, boundary_points); + } + } + return (m_boundary_segment_count - boundary_segment_count0); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, size_t point_stride, const float* boundary_points, bool bCloseBoundary) +{ + const ON__UINT32 boundary_segment_count0 = m_boundary_segment_count; + if (Internal_HaveWindingPoint() && point_count >= 2 && point_stride >= 2 && nullptr != boundary_points) + { + ON_2dPoint p0, p1; + const float* pmax = boundary_points + (point_count*point_stride); + p1.x = boundary_points[0]; + p1.y = boundary_points[1]; + for (const float* p = boundary_points+point_stride; p < pmax; p += point_stride) + { + p0 = p1; + p1.x = p[0]; + p1.y = p[1]; + Internal_AddBoundarySegment(&p0.x, &p1.x); + } + if (bCloseBoundary) + { + p0.x = boundary_points[0]; + p0.y = boundary_points[1]; + Internal_AddBoundarySegment(&p1.x, &p0.x); + } + } + return (m_boundary_segment_count - boundary_segment_count0); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, size_t point_stride, const int* boundary_points, bool bCloseBoundary) +{ + const ON__UINT32 boundary_segment_count0 = m_boundary_segment_count; + if (Internal_HaveWindingPoint() && point_count >= 2 && point_stride >= 2 && nullptr != boundary_points) + { + ON_2dPoint p0, p1; + const int* pmax = boundary_points + (point_count*point_stride); + p1.x = boundary_points[0]; + p1.y = boundary_points[1]; + for (const int* p = boundary_points+point_stride; p < pmax; p += point_stride) + { + p0 = p1; + p1.x = p[0]; + p1.y = p[1]; + Internal_AddBoundarySegment(&p0.x, &p1.x); + } + if (bCloseBoundary) + { + p0.x = boundary_points[0]; + p0.y = boundary_points[1]; + Internal_AddBoundarySegment(&p1.x, &p0.x); + } + } + return (m_boundary_segment_count - boundary_segment_count0); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, const ON_2dPoint* boundary_points, bool bCloseBoundary) +{ + const double* p = (nullptr != boundary_points ? &boundary_points->x : nullptr); + return AddBoundary(point_count, sizeof(boundary_points[0])/sizeof(p[0]), p, bCloseBoundary); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, const ON_3dPoint* boundary_points, bool bCloseBoundary) +{ + const double* p = (nullptr != boundary_points ? &boundary_points->x : nullptr); + return AddBoundary(point_count, sizeof(boundary_points[0])/sizeof(p[0]), p, bCloseBoundary); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, const ON_2fPoint* boundary_points, bool bCloseBoundary) +{ + const float* p = (nullptr != boundary_points ? &boundary_points->x : nullptr); + return AddBoundary(point_count, sizeof(boundary_points[0])/sizeof(p[0]), p, bCloseBoundary); +} + +ON__UINT32 ON_WindingNumber::AddBoundary(size_t point_count, const ON_3fPoint* boundary_points, bool bCloseBoundary) +{ + const float* p = (nullptr != boundary_points ? &boundary_points->x : nullptr); + return AddBoundary(point_count, sizeof(boundary_points[0])/sizeof(p[0]), p, bCloseBoundary); +} + + + + ON_PeriodicDomain::ON_PeriodicDomain(const ON_Interval dom[2], const bool closed[2], double normband) : m_dom{ dom[0], dom[1] }, m_closed{ closed[0],closed[1] }, @@ -9262,4 +9627,3 @@ ON_2dPoint ON_LiftInverse(ON_2dPoint P, ON_Interval dom[2], bool closed[2]) return Q; } - diff --git a/opennurbs_point.h b/opennurbs_point.h index 1fc5aa0e..2ef18669 100644 --- a/opennurbs_point.h +++ b/opennurbs_point.h @@ -3093,6 +3093,335 @@ bool operator==(const ON_4dRect&, const ON_4dRect&); ON_DECL bool operator!=(const ON_4dRect&, const ON_4dRect&); +/* +Description: + Tool for efficiently calculating a boundary polyline winding number + with input tools that do not require the boundary polyline to be + a contiguous array of points. +*/ +class ON_CLASS ON_WindingNumber +{ +public: + ON_WindingNumber() = default; + ~ON_WindingNumber() = default; + ON_WindingNumber(const ON_WindingNumber&) = default; + ON_WindingNumber& operator=(const ON_WindingNumber&) = default; + +public: + static const ON_WindingNumber Unset; + +public: + /* + Description: + The calculation of the winding number begins with a call to SetWindingPoint(). + Parameters: + x - [in] + y - [in] + The coordinates of the winding point are (x,y). + Remarks: + Calling SetWindingPoint() erases results of any previous calculations. + */ + void SetWindingPoint(double x, double y); + + /* + Description: + The calculation of the winding number begins with a call to SetWindingPoint(). + Parameters: + winding_point - [in] + Remarks: + Calling SetWindingPoint() erases results of any previous calculations. + */ + void SetWindingPoint(ON_2dPoint winding_point); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + p - [in] + next point in boundary. + Returns: + Number of boundary edge segments added. + */ + ON__UINT32 AddBoundary(ON_2dPoint p); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + p - [in] + start of edge segment. + Caller is responsible for insuring coordinates of p are valid doubles. + q - [in] + end of edge segment + Caller is responsible for insuring coordinates of q are valid doubles. + The calculation tolerates p==q. + Returns: + Number of boundary edge segments added. + */ + ON__UINT32 AddBoundary(ON_2dPoint p, ON_2dPoint q); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + point_stride - [in] >= 2 + The i-th point has coordinates (boundary_points[i*point_stride],boundary_points[i*point_stride+1]). + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, size_t point_stride, const double* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + point_stride - [in] >= 2 + The i-th point has coordinates (boundary_points[i*point_stride],boundary_points[i*point_stride+1]). + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, size_t point_stride, const float* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + point_stride - [in] >= 2 + The i-th point has coordinates (boundary_points[i*point_stride],boundary_points[i*point_stride+1]). + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, size_t point_stride, const int* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, const ON_2dPoint* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, const ON_3dPoint* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, const ON_2fPoint* boundary_points, bool bCloseBoundary); + + /* + Description: + After calling SetWindingPoint(), the boundary may be specified by one or more calls to + various AddBoundary functions. + The boundary may be specified one point at a time, one edge at a time, portions of the boundary + at a time, or the entire boundary in a single call. The edges may be added in any order. + The caller is responsible for insuring the collection calls to AddBoundary() results in a + a continuous, oriented, and closed polyline. + Parameters: + point_count - [in] >= 2 + number of points in boundary_points[] array. + boundary_points - [in] + Boundary points. + bCloseBoundary - [in] + If true, an edge segment is added from the last boundary point to the first boundary point. + Returns: + Number of boundary edge segments added. + Remarks: + The calculation tolerates zero length edge segments. The caller is responsible for insuring the + coordinates in boundary_points[] are valid doubles. + */ + ON__UINT32 AddBoundary(size_t point_count, const ON_3fPoint* boundary_points, bool bCloseBoundary); + + /* + Returns: + The winding number of the boundary about the winding point. + */ + ON__INT32 WindingNumber() const; + + /* + Returns: + The winding point. + */ + const ON_2dPoint WindingPoint() const; + + /* + Returns: + Number of segments in the boundary. + */ + ON__UINT32 BoundarySegmentCount() const; + + /* + Returns: + The end of the previous call to AddBoundary() + */ + const ON_2dPoint PreviousBoundaryPoint() const; + +private: + // Location of the winding point. + ON_2dPoint m_winding_point = ON_2dPoint::NanPoint; + + // Location of the last boundary point added. This is used + // by AddBoundary(ON_2dPoint p) to generate a segment + // from m_prev_boundary_point to p in situations where + // points are streamed so the caller doesn't have to + // deal with accumulating the previous point and can + // mix streamed points with other forms of boundary input. + ON_2dPoint m_prev_boundary_point = ON_2dPoint::NanPoint; + + // Number of boundary segments in the polyline + ON__UINT32 m_boundary_segment_count = 0; + + // In the comments below, H is the horizontal line throught the winding point + // and V is the vertical line through the winding point. + + // signed net number of times polyline crosses H to the left of the winding point. + // A below to above crossing is -1. + ON__INT32 m_left_crossing_number = 0; + + // signed net number of times polyline crosses H to the right of the winding point. + // A below to above crossing is +1. + ON__INT32 m_right_crossing_number = 0; + + // signed net number of times polyline crosses V to the below of the winding point. + // A left to right crossing is +1. + ON__INT32 m_below_crossing_number = 0; + + // signed net number of times polyline crosses V to the above of the winding point. + // A left to right crossing is -1. + ON__INT32 m_above_crossing_number = 0; + + // 0 != (m_status_bits & 1): left crossing occured + // 0 != (m_status_bits & 2): right crossing occured + // 0 != (m_status_bits & 4): below crossing occured + // 0 != (m_status_bits & 8): above crossing occured + // 0 != (m_status_bits & 16): winding point on horizontal segment + // 0 != (m_status_bits & 32): winding point on vertical segment + ON__INT32 m_status_bits = 0; + + void Internal_AddBoundarySegment(const double* p, const double* q); + + // Input: p and q are 2d points with p.y <= 0 and q.y > 0 + // + // Returns: + // Sign of the x coordinate of the intersection of the line segment from p to q + // and the x axis. + static int Internal_SignOfX(const ON_2dPoint& p, const ON_2dPoint& q); + + // Input: p and q are 2d points with p.x <= 0 and q.x > 0 + // + // Returns: + // Sign of the y coordinate of the intersection of the line segment from p to q + // and the y axis. + static int Internal_SignOfY(const ON_2dPoint& p, const ON_2dPoint& q); + + bool Internal_HaveWindingPoint() const; +}; + /* ON_PeriodicDomain is a helper class for dealing with closed or periodic surfaces using the idea of a covering space. diff --git a/opennurbs_public.h b/opennurbs_public.h index 00ef8a26..e04d6375 100644 --- a/opennurbs_public.h +++ b/opennurbs_public.h @@ -48,6 +48,36 @@ #define OPENNURBS_PUBLIC #include "opennurbs.h" +#if defined(ON_COMPILER_MSC) && defined(OPENNURBS_PUBLIC_INSTALL_DIR) + +#if defined(_DEBUG) +#if defined(ON_64BIT_RUNTIME) +#define OPENNURBS_PUBLIC_LIBS_DIR OPENNURBS_PUBLIC_INSTALL_DIR "/bin/x64/Debug" +#elif defined(ON_32BIT_RUNTIME) +#define OPENNURBS_PUBLIC_LIBS_DIR OPENNURBS_PUBLIC_INSTALL_DIR "/bin/Win32/Debug" +#endif +#else +#if defined(ON_64BIT_RUNTIME) +#define OPENNURBS_PUBLIC_LIBS_DIR OPENNURBS_PUBLIC_INSTALL_DIR "/bin/x64/Release" +#elif defined(ON_32BIT_RUNTIME) +#define OPENNURBS_PUBLIC_LIBS_DIR OPENNURBS_PUBLIC_INSTALL_DIR "/bin/Win32/Release" +#endif +#endif + +#if defined(OPENNURBS_IMPORTS) +#pragma message( " --- dynamically linking opennurbs (DLL)." ) +#pragma comment(lib, "\"" OPENNURBS_PUBLIC_LIBS_DIR "/" "opennurbs_public.lib" "\"") +#else +#pragma message( " --- statically linking opennurbs." ) +#pragma comment(lib, "\"" OPENNURBS_PUBLIC_LIBS_DIR "/" "opennurbs_public_staticlib.lib" "\"") +#pragma comment(lib, "\"" OPENNURBS_PUBLIC_LIBS_DIR "/" "zlib.lib" "\"") +#pragma comment(lib, "\"" OPENNURBS_PUBLIC_LIBS_DIR "/" "freetype263.lib" "\"") +#pragma comment(lib, "rpcrt4.lib") +#pragma comment(lib, "shlwapi.lib") +#endif + +#endif + #undef OPENNURBS_PUBLIC_INC_IN_PROGRESS #endif diff --git a/opennurbs_public.sln b/opennurbs_public.sln index 9c0afbad..6db51cc0 100644 --- a/opennurbs_public.sln +++ b/opennurbs_public.sln @@ -31,20 +31,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_write", "example_wr {23288C65-E3EB-4D09-A648-22E1636EB40F} = {23288C65-E3EB-4D09-A648-22E1636EB40F} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "freetype263", "freetype263\freetype263.vcxproj", "{426B0F99-1100-4CD6-9CB3-78C460817951}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "freetype263_staticlib", "freetype263\freetype263_staticlib.vcxproj", "{F28EFCCD-948B-425C-B9FC-112D84A6498D}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opennurbs_public", "opennurbs_public.vcxproj", "{1356641D-0B22-4123-B519-A69EE5CDC7F8}" ProjectSection(ProjectDependencies) = postProject - {426B0F99-1100-4CD6-9CB3-78C460817951} = {426B0F99-1100-4CD6-9CB3-78C460817951} {7B90C09F-DC78-42B2-AD34-380F6D466B29} = {7B90C09F-DC78-42B2-AD34-380F6D466B29} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opennurbs_public_staticlib", "opennurbs_public_staticlib.vcxproj", "{23288C65-E3EB-4D09-A648-22E1636EB40F}" ProjectSection(ProjectDependencies) = postProject {7B90C09F-DC78-42B2-AD34-380F6D466B29} = {7B90C09F-DC78-42B2-AD34-380F6D466B29} - {F28EFCCD-948B-425C-B9FC-112D84A6498D} = {F28EFCCD-948B-425C-B9FC-112D84A6498D} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_convert", "example_convert\example_convert.vcxproj", "{15C98F21-2AC9-44C8-8752-25DAFA1C739F}" @@ -107,22 +101,6 @@ Global {75A90363-D54A-4C56-B4FC-900E7540331C}.Release|Win32.Build.0 = Release|Win32 {75A90363-D54A-4C56-B4FC-900E7540331C}.Release|x64.ActiveCfg = Release|x64 {75A90363-D54A-4C56-B4FC-900E7540331C}.Release|x64.Build.0 = Release|x64 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Debug|Win32.ActiveCfg = Debug|Win32 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Debug|Win32.Build.0 = Debug|Win32 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Debug|x64.ActiveCfg = Debug|x64 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Debug|x64.Build.0 = Debug|x64 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Release|Win32.ActiveCfg = Release|Win32 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Release|Win32.Build.0 = Release|Win32 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Release|x64.ActiveCfg = Release|x64 - {426B0F99-1100-4CD6-9CB3-78C460817951}.Release|x64.Build.0 = Release|x64 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Debug|Win32.ActiveCfg = Debug|Win32 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Debug|Win32.Build.0 = Debug|Win32 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Debug|x64.ActiveCfg = Debug|x64 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Debug|x64.Build.0 = Debug|x64 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Release|Win32.ActiveCfg = Release|Win32 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Release|Win32.Build.0 = Release|Win32 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Release|x64.ActiveCfg = Release|x64 - {F28EFCCD-948B-425C-B9FC-112D84A6498D}.Release|x64.Build.0 = Release|x64 {1356641D-0B22-4123-B519-A69EE5CDC7F8}.Debug|Win32.ActiveCfg = Debug|Win32 {1356641D-0B22-4123-B519-A69EE5CDC7F8}.Debug|Win32.Build.0 = Debug|Win32 {1356641D-0B22-4123-B519-A69EE5CDC7F8}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/opennurbs_public.vcxproj b/opennurbs_public.vcxproj index 4090356f..3d982113 100644 --- a/opennurbs_public.vcxproj +++ b/opennurbs_public.vcxproj @@ -181,8 +181,6 @@ - - @@ -339,9 +337,6 @@ - - $(OpennurbsRootDir)/freetype263/include - diff --git a/opennurbs_public.xcodeproj/project.pbxproj b/opennurbs_public.xcodeproj/project.pbxproj index f1fa4241..4855b139 100644 --- a/opennurbs_public.xcodeproj/project.pbxproj +++ b/opennurbs_public.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 1D21BE19208A731400C62038 /* opennurbs_glyph_outline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D21BE18208A731400C62038 /* opennurbs_glyph_outline.cpp */; }; 1D4452FF1ED6454500CD7FC1 /* opennurbs.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D4452FE1ED6454500CD7FC1 /* opennurbs.h */; }; + 1D455699216D9DA100BC992F /* opennurbs_apple_nsfont.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */; }; + 1D45569B216D9DAE00BC992F /* opennurbs_apple_nsfont.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */; }; 1D7B99581FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */; }; 1DBFBF3A1EDF3092005B50AF /* opennurbs_public_memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DBFBF391EDF3092005B50AF /* opennurbs_public_memory.cpp */; }; 1DBFBF3C1EDF333C005B50AF /* opennurbs_memory_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DBFBF3B1EDF333C005B50AF /* opennurbs_memory_util.cpp */; }; @@ -336,6 +338,8 @@ /* Begin PBXFileReference section */ 1D21BE18208A731400C62038 /* opennurbs_glyph_outline.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_glyph_outline.cpp; sourceTree = ""; }; 1D4452FE1ED6454500CD7FC1 /* opennurbs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs.h; sourceTree = ""; }; + 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_apple_nsfont.cpp; sourceTree = ""; }; + 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs_apple_nsfont.h; sourceTree = ""; }; 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_unicode_cpsb.cpp; sourceTree = ""; }; 1DB0280D1ED6421900FA9144 /* libopennurbs_public.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libopennurbs_public.a; sourceTree = BUILT_PRODUCTS_DIR; }; 1DBFBF391EDF3092005B50AF /* opennurbs_public_memory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_public_memory.cpp; sourceTree = ""; }; @@ -696,6 +700,7 @@ 1DC319EF1ED653EE00DE6D26 /* Header Files */ = { isa = PBXGroup; children = ( + 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */, 1DC317611ED652B700DE6D26 /* opennurbs_3dm_attributes.h */, 1DC317631ED652B700DE6D26 /* opennurbs_3dm_properties.h */, 1DC317651ED652B700DE6D26 /* opennurbs_3dm_settings.h */, @@ -853,6 +858,7 @@ 1DC319F01ED6546C00DE6D26 /* Source Files */ = { isa = PBXGroup; children = ( + 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */, 1D21BE18208A731400C62038 /* opennurbs_glyph_outline.cpp */, 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */, 1DBFBF3B1EDF333C005B50AF /* opennurbs_memory_util.cpp */, @@ -1167,6 +1173,7 @@ 1DC319E81ED6534E00DE6D26 /* opennurbs_workspace.h in Headers */, 1DC317F21ED652B800DE6D26 /* opennurbs_brep.h in Headers */, 1DC318E11ED652F800DE6D26 /* opennurbs_mesh.h in Headers */, + 1D45569B216D9DAE00BC992F /* opennurbs_apple_nsfont.h in Headers */, 1DC318B31ED652F800DE6D26 /* opennurbs_internal_V2_annotation.h in Headers */, 1DC319C61ED6534E00DE6D26 /* opennurbs_textdraw.h in Headers */, 1DC319091ED652F800DE6D26 /* opennurbs_pointgeometry.h in Headers */, @@ -1354,6 +1361,7 @@ 1DC318B81ED652F800DE6D26 /* opennurbs_internal_Vx_annotation.cpp in Sources */, 1DC319DD1ED6534E00DE6D26 /* opennurbs_uuid.cpp in Sources */, 1D21BE19208A731400C62038 /* opennurbs_glyph_outline.cpp in Sources */, + 1D455699216D9DA100BC992F /* opennurbs_apple_nsfont.cpp in Sources */, 1DC318A61ED652F800DE6D26 /* opennurbs_group.cpp in Sources */, 1DC319C51ED6534E00DE6D26 /* opennurbs_textdraw.cpp in Sources */, 1DC318011ED652B800DE6D26 /* opennurbs_curve.cpp in Sources */, diff --git a/opennurbs_public.xcworkspace/contents.xcworkspacedata b/opennurbs_public.xcworkspace/contents.xcworkspacedata index 73b2cd35..f604976e 100644 --- a/opennurbs_public.xcworkspace/contents.xcworkspacedata +++ b/opennurbs_public.xcworkspace/contents.xcworkspacedata @@ -23,9 +23,6 @@ - - diff --git a/opennurbs_public_examples.h b/opennurbs_public_examples.h index 7f22f43f..eef21a5f 100644 --- a/opennurbs_public_examples.h +++ b/opennurbs_public_examples.h @@ -42,7 +42,9 @@ #pragma message( " --- statically linking opennurbs." ) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "opennurbs_public_staticlib.lib" "\"") #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "zlib.lib" "\"") +#if defined(OPENNURBS_FREETYPE_SUPPORT) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "freetype263_staticlib.lib" "\"") +#endif #pragma comment(lib, "rpcrt4.lib") #pragma comment(lib, "shlwapi.lib") #endif diff --git a/opennurbs_public_staticlib.vcxproj b/opennurbs_public_staticlib.vcxproj index e36b66e7..fd6bd9dd 100644 --- a/opennurbs_public_staticlib.vcxproj +++ b/opennurbs_public_staticlib.vcxproj @@ -181,8 +181,6 @@ - - @@ -338,9 +336,6 @@ - - $(OpennurbsRootDir)/freetype263/include - diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 0d457cf4..fa99817b 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,7 +6,7 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 6 -#define RMA_VERSION_MINOR 8 +#define RMA_VERSION_MINOR 11 //////////////////////////////////////////////////////////////// // @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2018 -#define RMA_VERSION_MONTH 8 -#define RMA_VERSION_DATE 28 -#define RMA_VERSION_HOUR 20 -#define RMA_VERSION_MINUTE 5 +#define RMA_VERSION_MONTH 10 +#define RMA_VERSION_DATE 9 +#define RMA_VERSION_HOUR 1 +#define RMA_VERSION_MINUTE 0 //////////////////////////////////////////////////////////////// // @@ -33,21 +33,21 @@ // 1 = build system trunk build // 2 = build system release candidate build // 3 = build system release build -#define RMA_VERSION_BRANCH 1 +#define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 6,8,18240,20051 -#define VERSION_WITH_PERIODS 6.8.18240.20051 +#define VERSION_WITH_COMMAS 6,11,18282,1000 +#define VERSION_WITH_PERIODS 6.11.18282.01000 #define COPYRIGHT "Copyright (C) 1993-2018, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." #define RMA_VERSION_NUMBER_MAJOR_STRING "6" #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"6" -#define RMA_VERSION_NUMBER_SR_STRING "SR8" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR8" +#define RMA_VERSION_NUMBER_SR_STRING "SR11" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR11" -#define RMA_VERSION_WITH_PERIODS_STRING "6.8.18240.20051" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"6.8.18240.20051" +#define RMA_VERSION_WITH_PERIODS_STRING "6.11.18282.01000" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"6.11.18282.01000" diff --git a/opennurbs_statics.cpp b/opennurbs_statics.cpp index f682feb3..4ee44337 100644 --- a/opennurbs_statics.cpp +++ b/opennurbs_statics.cpp @@ -607,6 +607,8 @@ const ON_3fVector ON_3fVector::XAxis(1.0f, 0.0f, 0.0f); const ON_3fVector ON_3fVector::YAxis(0.0f, 1.0f, 0.0f); const ON_3fVector ON_3fVector::ZAxis(0.0f, 0.0f, 1.0f); +const ON_WindingNumber ON_WindingNumber::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_WindingNumber); + static ON_BoundingBox BoundingBoxInit(double x) { ON_BoundingBox bbox; @@ -1033,6 +1035,8 @@ const ON_2dSize ON_2dSize::Unset(ON_UNSET_VALUE, ON_UNSET_VALUE); const ON_4dRect ON_4dRect::Zero(0.0, 0.0, 0.0, 0.0); const ON_4dRect ON_4dRect::Unset(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); +const ON_OutlineFigure::Orientation ON_Outline::DefaultOuterOrientation = ON_OutlineFigure::Orientation::Clockwise; + const ON_OutlineFigurePoint ON_OutlineFigurePoint::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_OutlineFigurePoint); static ON_OutlineFigurePoint Internal_FontGlyphOutlinePoint() { @@ -1322,6 +1326,8 @@ const ON_ModelComponentTypeIterator ON_ModelComponentTypeIterator::TableComponen const ON_ModelComponent ON_ModelComponent::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ModelComponent); +const ON_ModelComponentReference ON_ModelComponentReference::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ModelComponentReference); +const ON_ModelComponentWeakReference ON_ModelComponentWeakReference::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ModelComponentWeakReference); const ON_ModelComponentContentMark ON_ModelComponentContentMark::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ModelComponentContentMark); @@ -2302,7 +2308,18 @@ const unsigned int ON_SubDVertex::MaximumFaceCount = 0xFFF0U; const unsigned int ON_SubDEdge::MaximumFaceCount = 0xFFF0U; const unsigned int ON_SubDFace::MaximumEdgeCount = 0xFFF0U; +const ON_SubDComponentRegionIndex ON_SubDComponentRegionIndex::Zero ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDComponentRegionIndex); + +static ON_SubDComponentRegionIndex InternalON_SubDComponentRegionIndex_Unset() +{ + ON_SubDComponentRegionIndex unset; + memset(&unset, 0xFF, sizeof(unset)); + return unset; +} +const ON_SubDComponentRegionIndex ON_SubDComponentRegionIndex::Unset = InternalON_SubDComponentRegionIndex_Unset(); + const ON_SubDComponentRegion ON_SubDComponentRegion::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDComponentRegion); +const ON_SubDFaceRegion ON_SubDFaceRegion::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDFaceRegion); const ON_ComponentStatus ON_ComponentStatus::NoneSet = ON_ComponentStatus(ON_ComponentState::Clear); const ON_ComponentStatus ON_ComponentStatus::Selected = ON_ComponentStatus(ON_ComponentState::Selected); diff --git a/opennurbs_string.h b/opennurbs_string.h index 07a1bfb0..08ab5313 100644 --- a/opennurbs_string.h +++ b/opennurbs_string.h @@ -947,6 +947,13 @@ public: bool LoadResourceString( HINSTANCE, UINT); // load from Windows string resource // 2047 chars max #endif + +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + ON_String(CFStringRef); + + CFStringRef ToAppleCFString() const; +#endif + /* Parameters: bLengthTest - [in] @@ -2200,6 +2207,12 @@ public: // 2047 characters max #endif + +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + ON_wString(CFStringRef); + + CFStringRef ToAppleCFString() const; +#endif /* Parameters: bLengthTest - [in] @@ -3143,6 +3156,58 @@ public: double d // "%.17g" ); + /* + Parameters: + date_format - [in] + date_format - [in] + time_format - [in] + date_separator - [in] + Character placed between the year, month and day values. + If 0, then ON_wString::HyphenMinus is used to + date_time_separator - [in] + Character placed between the date and time. + If 0, then ON_wString::Space is used. + time_separator - [in] + Character placed between the hour, minute, and second values. + If 0, then : (colon) is used. + Returns: + A string value for the current coordinated universal time (UTC). + */ + + static const ON_wString FromCurrentCoordinatedUniversalTime( + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator + ); + + /* + Parameters: + t - [in] + time to format + date_format - [in] + date_format - [in] + time_format - [in] + date_separator - [in] + Character placed between the year, month and day values. + If 0, then ON_wString::HyphenMinus is used to + date_time_separator - [in] + Character placed between the date and time. + If 0, then ON_wString::Space is used. + time_separator - [in] + Character placed between the hour, minute, and second values. + If 0, then : (colon) is used. + */ + static const 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 + ); + /* Description: Convert a list of Unicode code points into a wide string. diff --git a/opennurbs_string_format.cpp b/opennurbs_string_format.cpp index 0d185865..1ed91a05 100644 --- a/opennurbs_string_format.cpp +++ b/opennurbs_string_format.cpp @@ -355,6 +355,143 @@ const ON_wString ON_wString::PreciseFromNumber( 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::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; + + bool bValidDate = (year >= 1900 && mday > 0 && yday > 0 && mon > 0); + bool bValidHMS = (t.tm_hour >= 0 && t.tm_min >= 0 && t.tm_sec >= 0 && t.tm_hour < 24 && t.tm_min <= 59 && t.tm_sec <= 59); + + if (0 == date_separator) + date_separator = ON_wString::HyphenMinus; + if (0 == time_separator) + time_separator = ':'; + + const wchar_t ds[2] = { date_separator,0 }; + const wchar_t ts[2] = { time_separator,0 }; + const wchar_t* ampm = (t.tm_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, mon, ds, mday); + break; + case ON_DateFormat::YearDayMonth: + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, mday, ds, mon); + break; + case ON_DateFormat::MonthDayYear: + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mon, ds, mday, ds, year); + break; + case ON_DateFormat::DayMonthYear: + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mday, ds, mon, 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"%d%ls%d%ls", t.tm_hour%12, ts, t.tm_min, ampm); + break; + case ON_TimeFormat::HourMinuteSecond12: + hms = ON_wString::FormatToString(L"%d%ls%d%ls%d%ls", t.tm_hour%12, ts, t.tm_min, ts, t.tm_sec, ampm); + break; + case ON_TimeFormat::HourMinute24: + hms = ON_wString::FormatToString(L"%d%ls%d", t.tm_hour, ts, t.tm_min); + break; + case ON_TimeFormat::HourMinuteSecond24: + hms = ON_wString::FormatToString(L"%d%ls%d%ls%d", t.tm_hour, ts, t.tm_min, ts, t.tm_sec); + break; + default: + break; + } + + ON_wString result = date; + if (result.IsNotEmpty() && hms.IsNotEmpty()) + { + if (0 == date_time_separator) + date_time_separator = ON_wString::Space; + 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, diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index 325eb1b3..fc340f94 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -667,7 +667,7 @@ ON_SubDComponentPtr ON_SubDComponentPtr::ToggleMark() const return (0 != (m_ptr & ON_SUBD_ELEMENT_MARK_MASK)) ? ClearMark() : SetMark(); } -class ON_SubDComponentPtr ON_SubDComponentPtr::CreateNull( +const ON_SubDComponentPtr ON_SubDComponentPtr::CreateNull( ON_SubDComponentPtr::Type component_type, bool bMark ) @@ -767,7 +767,7 @@ ON_SubDFacePtr ON_SubDComponentPtr::FacePtr() const return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null); } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDVertex* vertex ) { @@ -779,7 +779,7 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( return ON_SubDComponentPtr::Null; } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDEdge* edge ) { @@ -792,7 +792,7 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDFace* face ) { @@ -804,7 +804,7 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( return ON_SubDComponentPtr::Null; } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDVertex* vertex, ON__UINT_PTR vertex_direction ) @@ -817,7 +817,7 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( return ON_SubDComponentPtr::Null; } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDEdge* edge, ON__UINT_PTR edge_direction ) @@ -830,7 +830,7 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( return ON_SubDComponentPtr::Null; } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( const class ON_SubDFace* face, ON__UINT_PTR face_direction ) @@ -843,21 +843,21 @@ class ON_SubDComponentPtr ON_SubDComponentPtr::Create( return ON_SubDComponentPtr::Null; } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( ON_SubDVertexPtr vertexptr ) { return Create(vertexptr.Vertex(), vertexptr.VertexPtrMark()); } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( ON_SubDEdgePtr edgeptr ) { return Create(edgeptr.Edge(), edgeptr.EdgeDirection()); } -class ON_SubDComponentPtr ON_SubDComponentPtr::Create( +const ON_SubDComponentPtr ON_SubDComponentPtr::Create( ON_SubDFacePtr faceptr ) { diff --git a/opennurbs_subd.h b/opennurbs_subd.h index 5790d77d..5a4a3c31 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -342,58 +342,66 @@ public: ON_SubDComponentPtr ToggleMark() const; static - class ON_SubDComponentPtr CreateNull( + const ON_SubDComponentPtr CreateNull( ON_SubDComponentPtr::Type component_type, bool bMark ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDVertex* vertex ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDEdge* edge ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDFace* face ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDVertex* vertex, ON__UINT_PTR vertex_mark ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDEdge* edge, ON__UINT_PTR edge_mark ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( const class ON_SubDFace* face, ON__UINT_PTR face_mark ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( ON_SubDVertexPtr vertexptr ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( ON_SubDEdgePtr edgeptr ); static - class ON_SubDComponentPtr Create( + const ON_SubDComponentPtr Create( ON_SubDFacePtr faceptr ); + + wchar_t* ToString( + wchar_t* s, + size_t s_capacity + ) const; + + const ON_wString ToString() const; + }; #if defined(OPENNURBS_SUBD_WIP) @@ -406,6 +414,455 @@ public: #define ON_SUBD_CLASS #endif +class ON_SUBD_CLASS ON_SubDComponentRegionIndex +{ +public: + ON_SubDComponentRegionIndex() = default; + ~ON_SubDComponentRegionIndex() = default; + ON_SubDComponentRegionIndex(const ON_SubDComponentRegionIndex&) = default; + ON_SubDComponentRegionIndex& operator=(const ON_SubDComponentRegionIndex&) = default; + +public: + enum : unsigned short + { + /// Capacity of the m_index[] array; + IndexCapacity = 9 + }; + + // All values are zero + static const ON_SubDComponentRegionIndex Zero; + + // All values are 0xFFFF + static const ON_SubDComponentRegionIndex Unset; + + /* + Description: + Compares subdivision counts. If the counts are the same, compares m_indices[]. + */ + static int Compare( + const ON_SubDComponentRegionIndex* lhs, + const ON_SubDComponentRegionIndex* rhs + ); + + /* + Description: + Compares subdivision indices for minimum(lhs->m_subdivision_count,rhs->m_subdivision_count). + */ + static int CompareMinimumSubregion( + const ON_SubDComponentRegionIndex* lhs, + const ON_SubDComponentRegionIndex* rhs + ); + + unsigned short Index( + unsigned short i + ) const; + + unsigned short m_subdivision_count = 0; + + // If m_subdivision_count > 0, then m_index[0], ..., m_index[m_subdivision_count-1] + // identifies a subregion of the level 0 component. + // + // Faces with quad subdivision: + // m_index[n] is the subdivision quad for the region that contains + // the parent face's corner at face->m_vertex[m_index[n]]. + // Edges + // m_region_index[n] = 0 indicates the beginning half of the parent edge. + // (begins at edge->Vertex(0)) + // m_region_index[n] = 1 indicates the ending half of the parent edge. + // (ends at edge->Vertex(1)) + // + // When a component is created during a subdivision step, the value 0xFFFF + // is used to mark the non-existent regions at earlier subdivision levels. + // For example, if a level 1 edge is created by connecting + // a level0 edge subdivision point (middle-ish of the edge) + // to a level0 face subdivision point (center-ish of the face), + // then the level 1 edge would have + // m_level0_component = ON_SubDComponentPtr::CreateNull(ON_SubDComponentPtr::Type::Edge, bReversed), + // (m_level0_component.IsNull() will be true) + // m_level0_component_id = ON_SubDComponentRegion::NewTransientId() + // m_subdivision_count = 1, + // m_region_index[0] = 0xFFFF. + // + unsigned short m_index[ON_SubDComponentRegionIndex::IndexCapacity] = {}; + + void Push( + unsigned int region_index + ); + + void Pop(); + + /* + Description: + Get a string of the form .a.b.c .a.b.c = m_index[] values. + */ + wchar_t* ToString( + wchar_t* s, + size_t s_capacity + ) const; + + const ON_wString ToString() const; + + /* + Description: + Encodes ON_SubDComponentRegionIndex information in 32 bits. + (m_subdivision_count) << 24 + | (0x00FF0000 & ((m_region_index[0]) << 16)) + | (m_region_index[1] & 0x0003) << (14) + | (m_region_index[2] & 0x0003) << (12) + ... + | (m_index[m_subdivision_count] & 0x0003) <<(16-(2*m_subdivision_count)) + Remarks: + This is useful when quick compare and sorting of regions is required, + m_subdivision_count < 256, m_index[0] < 256, m_index[1] < 4, ..., m_index[m_subdivision_count] < 4 + Regions of N-gons with N < 256 and regions of edges + satisify these condition when m_subdivision_count < 256 + (which is always in real world situations). + */ + ON__UINT32 ToCompressedRegionIndex() const; + + static const ON_SubDComponentRegionIndex FromCompressedRegionIndex( + ON__UINT32 compressed_region_index + ); + + static ON__UINT32 ToCompressedRegionIndex( + unsigned short subdivision_count, + const unsigned short* region_index + ); + + /* + Description: + Decompress a 32-bit region. + Parameters: + region32 - [in] + Value returned from To32BitRegion(). + subdivision_count - [out] + Subdivision count + region_index[] - out + Region indices. The region_index[] array must have a capcity of at + least ON_SubDComponentRegion::region_index_capacity elements. + */ + static void FromCompressedRegionIndex( + ON__UINT32 compressed_region_index, + unsigned short* subdivision_count, + unsigned short* region_index + ); +}; + +class ON_SUBD_CLASS ON_SubDComponentRegion +{ +public: + ON_SubDComponentRegion() = default; + ~ON_SubDComponentRegion() = default; + ON_SubDComponentRegion(const ON_SubDComponentRegion&) = default; + ON_SubDComponentRegion& operator=(const ON_SubDComponentRegion&) = default; + +public: + static const ON_SubDComponentRegion Create( + const class ON_SubDFace* level0_face + ); + + static const ON_SubDComponentRegion Create( + unsigned int component_id, + ON_SubDComponentPtr::Type component_type, + bool bComponentMark + ); + + /* + Description: + Creates a region that can be used to identify a component + created at a certain level of subdivision that does not + come from dividing a component from the previous level. + For example, Catmull Clark subdivision edges on level N+1 + that run from the level N edge subdivision point to the + level N face subdivision point. + + m_level0_component = ON_SubDComponentPtr::CreateNull(component_type, bComponentMark), + (m_level0_component.IsNull() will be true) + m_level0_component_id = ON_SubDComponentRegion::NewTransientId() + m_subdivision_count = subdivision_count, + m_region_index[0, ..., (subdivision_count-1)] = 0xFFFF. + + */ + static const ON_SubDComponentRegion CreateSubdivisionRegion( + ON_SubDComponentPtr::Type component_type, + bool bComponentMark, + unsigned short subdivision_count, + bool bAssignTransientId + ); + +public: + static const ON_SubDComponentRegion Empty; + +public: + ON_SubDComponentPtr m_level0_component = ON_SubDComponentPtr::Null; + + unsigned int m_level0_component_id = 0; + + unsigned short SubdivisionCount() const; + + ON_SubDComponentRegionIndex m_region_index; + + /* + Returns: + True if m_level0_component_id is a transient id. + */ + bool IsTransientId() const; + + /* + Returns: + True if m_level0_component_id is the id of a persistent ON_SubD level 0 component. + */ + bool IsPersistentId() const; + + /* + Description: + Compares + m_level0_component.ComponentType(), + m_level0_component_id, + m_level0_component.ComponentMark(), + the entire sub region, + and m_level0_component.m_ptr. + */ + static int Compare( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs + ); + + /* + Descriptions: + Compares + m_level0_component.ComponentType(), + m_level0_component_id, + m_level0_component.ComponentMark(). + */ + static int CompareTypeIdMark( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs + ); + + /* + Description: + Compares + m_level0_component.ComponentType(), + m_level0_component_id, + m_level0_component.ComponentMark(), + and the m_region_index[] values for the + minimum subdivision count lhs and rhs. + */ + static int CompareTypeIdMarkMinimumSubregion( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs + ); + + /* + Description: + Compares + m_level0_component.ComponentType(), + m_level0_component_id, + m_level0_component.ComponentMark(), + and the entire sub region. + */ + static int CompareTypeIdMarkSubregion( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs + ); + + void SetLevel0Component( + ON_SubDComponentPtr component_ptr + ); + + void SetLevel0Face( + const ON_SubDFace* face + ); + + void SetLevel0EdgePtr( + const ON_SubDEdgePtr edge_ptr + ); + + void SetLevel0Vertex( + const ON_SubDVertex* vertex + ); + + /* + Description: + region_index - [in] + If m_level0_component identifies an edge, region_index is 0 or 1, + and the edge is reversed (1=m_level0_component.ComponentMark()), + then PushAbsolute(1-region_index) is called. + In every other case, PushAbsolute(region_index) is called. + */ + void PushAdjusted( + unsigned int region_index + ); + + /* + Parameters: + region_index - [in] + If m_level0_component identifies a face, then region_index is the index of the + corner vertex for the subdivision quad. + If m_level0_component identifies an edge, then region_index must be 0 or 1. + Description: + Increments if m_subdivision_count and appends region_index to m_region_index[] + (m_region_index[m_subdivision_count++] = region_index) + */ + void PushAbsolute( + unsigned int region_index + ); + + /* + Description: + Get a string of the form fN.a.b.c where N = m_level0_face-m_id, a.b.c = m_region_index[] values. + */ + wchar_t* ToString( + wchar_t* s, + size_t s_capacity + ) const; + + const ON_wString ToString() const; + + void Pop(); + + bool IsEmptyRegion() const; + + enum : unsigned int + { + TransientIdBit = 0x80000000U + }; + + + /* + Returns: + A value that can be used to identify transient subdivision components that do not + exist in the persistent levels of a SubD. + Transient ids always satisfy (ON_SubDComponentRegion::TransientIdBit & transient_id) is not zero and + (~ON_SubDComponentRegion::TransientIdBit & transient_id) is not zero. + Remarks: + Transient ids are used to identify subdivision components at levels that do + not persist in the ON_SubD. They are unique within the context where they are + being used. They generally vary with each repetition of a calcultion in that + context. + */ + static const unsigned int NewTransientId(); + + /* + Description: + Resets the value used to generate transient ids. + This is useful during debugging session so that transient id + values are predictable. Otherwise, use of this function + should be avoided. + */ + static void ResetTransientId(); + + /* + Parameters: + id - [in] + Value to test to see if it is a transient subd component id. + Returns: + True if (ON_SubDComponentRegion::TransientIdBit & id) is not zero and + (~ON_SubDComponentRegion::TransientIdBit & id) is not zero. + Remarks: + Transient ids are used to identify subdivision components at levels that do + not persist in the ON_SubD. They are unique within the context where they are + being used. They generally vary with each repetition of a calcultion in that + context. + */ + static bool IsTransientId(unsigned int id); + + /* + Parameters: + id - [in] + Value to test to see if it is a transient subd component id. + Returns: + If the id is a transient id, then its id value is returned. + Otherwise, 0 is returned. + Remarks: + Transient ids are used to identify subdivision components at levels that do + not persist in the ON_SubD. They are unique within the context where they are + being used. They generally vary with each repetition of a calcultion in that + context. + */ + static unsigned int TransientId(unsigned int id); + + /* + Parameters: + id - [in] + Value to test to see if it is a persitsent subd component id. + Returns: + True if (ON_SubDComponentRegion::TransientIdBit & id) is not zero and + (~ON_SubDComponentRegion::TransientIdBit & id) is not zero. + Remarks: + Transient ids are used to identify subdivision components at levels that do + not persist in the ON_SubD. They are unique within the context where they are + being used. They generally vary with each repetition of a calcultion in that + context. + */ + static bool IsPersistentId(unsigned int id); +}; + +class ON_SUBD_CLASS ON_SubDFaceRegion +{ +public: + ON_SubDFaceRegion() = default; + ~ON_SubDFaceRegion() = default; + ON_SubDFaceRegion(const ON_SubDFaceRegion&) = default; + ON_SubDFaceRegion& operator=(const ON_SubDFaceRegion&) = default; + + static const ON_SubDFaceRegion Empty; + +public: + // Identifies a region of an ON_SubDFace + ON_SubDComponentRegion m_face_region; + + // When the face region is a quad, m_edge_region[4] identifies regions of ON_SubDEdge elements. + // When the face region is a sub-quad, these edges may be null or have null ON_SubDEdge pointers + // and the ids will be zero or ON_SubDComponentRegion::IsTransientId() will be true. + // When ON_SubDComponentRegion::IsTransientId() is true, the id does not identify + // a persistent edge in the ON_SubD. + ON_SubDComponentRegion m_edge_region[4]; + + unsigned int m_level0_edge_count = 0; + + // If set, these are the vertice ids at the region's limit surface corners. + // m_vertex_id[] is mutable because these values appear during recursive calculations. + // When the face region is a sub-quad, these ids will be zero or ON_SubDComponentRegion::IsTransientId() + // will be true. + // When ON_SubDComponentRegion::IsTransientId() is true, the id does not identify + // a persistent vertex in the ON_SubD. + mutable unsigned int m_vertex_id[4] = {}; + +public: + void Push(unsigned int quadrant_index); + + bool IsValid( + bool bSilentError + ) const; + + wchar_t* ToString( + wchar_t* s, + size_t s_capacity + ) const; + + const ON_wString ToString() const; +}; + + +class ON_SUBD_CLASS ON_SubDFaceRegionAndNurbs +{ +public: + ON_SubDFaceRegionAndNurbs() = default; + ~ON_SubDFaceRegionAndNurbs() = default; + ON_SubDFaceRegionAndNurbs(const ON_SubDFaceRegionAndNurbs&) = default; + ON_SubDFaceRegionAndNurbs& operator=(const ON_SubDFaceRegionAndNurbs&) = default; + + static const ON_SubDFaceRegionAndNurbs Empty; + +public: + ON_SubDFaceRegion m_face_region; + // This pointer is not managed by ON_SubDFaceRegionAndNurbs + class ON_NurbsSurface* m_nurbs_surface = nullptr; +}; + + /* Description: The ON_SubDComponentLocation enum is used when an ON_SubD component @@ -2113,15 +2570,14 @@ public: bool begin_face_callback_function( void *fragment_callback_context, - const class ON_SubDFace* level0_face, - const class ON_SubDFace* level1_face, - unsigned int level1_face_region_index + const class ON_SubDFaceRegion& face_region, ); - At the beginning of each face, this function is called. - If an initial subdivison of an extraodinary face was required, - level1_face is not null and level1_face_region_index identifies the region - of level0_face that it covers. + At the beginning of each quad face, this function is called. + If the original SubD face is a quad, then face_region identifies that quad. + If the original SubD face is not a quad, then face_region identifies the + level 1 subdivision quad. The face region information is useful in building + a correspondence between the original SubD and the Nurbs patches. If begin_face_callback_function returns false, the calculation is canceled. fragment_callback_function - [in] @@ -2141,7 +2597,7 @@ public: unsigned int GetLimitSurfaceNurbsFragments( const class ON_SubDDisplayParameters& display_parameters, ON__UINT_PTR callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), + bool(*begin_face_callback_function)(ON__UINT_PTR ,const class ON_SubDFaceRegion&), bool(*fragment_callback_function)(ON__UINT_PTR ,const class ON_SubDLimitNurbsFragment*) ) const; @@ -2215,10 +2671,12 @@ public: models. subd_edge_region[4] identifies the edges the NURBS surface abuts in - the order South,East,North,West. When an edge is a subdivision edge, + the order South,East,North,West. + + When an edge is a subdivision edge, then subd_edge_region[i].m_level0_component.ComponentType() is ON_SubDComponentPtr::Type::Edge, subd_edge_region[i].m_level0_component.ComponentBase() is nullptr, and - subd_edge_region[i].m_level0_component_id is 0x80000000 | ... + ON_SubDComponentRegion::IsTransientId(subd_edge_region[i].m_level0_component_id) is true The nurbs_surface pointer points to an ON_NurbsSurface on the heap. You must take responsibility for managing this surface and deleting it @@ -2232,7 +2690,7 @@ public: const class ON_SubDDisplayParameters& display_parameters, ON_SubD::NurbsSurfaceType nurbs_surface_type, ON__UINT_PTR callback_context, - bool(*nurbs_callback_function)(ON__UINT_PTR , const class ON_SubDComponentRegion&, const class ON_SubDComponentRegion*, class ON_NurbsSurface*) + bool(*nurbs_callback_function)(ON__UINT_PTR , const class ON_SubDFaceRegion&, class ON_NurbsSurface*) ) const; @@ -2244,10 +2702,6 @@ public: Determines the density of NURBS patches near extraordinary vertices. nurbs_surface_type - [in] Controls the size and knot properties of the returned NURBS sufaces. - sUserStringPatchKey - [in] - If non empty, a user string with this key will be added that - contains the SubD face region information (from ON_SubDComponentRegion.ToString) - for the NURBS surface. patches - [out] The bicubic NURBS patches are appended to this array. Returns: @@ -2256,10 +2710,20 @@ public: unsigned int GetLimitSurfaceNurbs( const class ON_SubDDisplayParameters& display_parameters, ON_SubD::NurbsSurfaceType nurbs_surface_type, - const wchar_t* sUserStringPatchKey, ON_SimpleArray< ON_NurbsSurface* >& patches ) const; + unsigned int GetLimitSurfaceNurbs( + const class ON_SubDDisplayParameters& display_parameters, + ON_SubD::NurbsSurfaceType nurbs_surface_type, + ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches + ) const; + + ON_Brep* GetLimitSurfaceNurbs( + const class ON_SubDDisplayParameters& display_parameters, + ON_Brep* destination_brep + ) const; + public: /* Description: @@ -3931,108 +4395,7 @@ private: }; -class ON_SUBD_CLASS ON_SubDComponentRegion -{ -public: - ON_SubDComponentRegion() = default; - ~ON_SubDComponentRegion() = default; - ON_SubDComponentRegion(const ON_SubDComponentRegion&) = default; - ON_SubDComponentRegion& operator=(const ON_SubDComponentRegion&) = default; -public: - static const ON_SubDComponentRegion Create( - const class ON_SubDFace* level0_face - ); - - static const ON_SubDComponentRegion Create( - unsigned int component_id, - ON_SubDComponentPtr::Type component_type, - bool bComponentMark - ); - -public: - static const ON_SubDComponentRegion Empty; - -public: - ON_SubDComponentPtr m_level0_component = ON_SubDComponentPtr::Null; - unsigned int m_level0_component_id = 0; - - unsigned short m_subdivision_count = 0; - - enum : unsigned short - { - region_index_capacity = 9 - }; - - // If m_subdivision_count > 0, then m_region_index[0], ..., m_region_index[m_subdivision_count-1] - // identifies a subregion of the level 0 component. - // Faces with quad subdivision: - // m_region_index[n] is the subdivision quad for the region contain the parent face->m_vertex[m_region_index[n]]. - // Edges - // m_region_index[n] = 0 indicates the beginning half of the parent edge. - // m_region_index[n] = 1 indicates the ending half of the parent edge. - unsigned short m_region_index[ON_SubDComponentRegion::region_index_capacity] = {}; - - /* - Returns: - Returns a region with the value m_level0_component.ComponentMark() toggled - and the active portion of the m_region_index[] array reversed. - Description: - Useful for standardizing edge regions when searching for duplicates. - */ - ON_SubDComponentRegion Reverse() const; - - /* - Returns: - Returns a region with the value m_level0_component.ComponentMark() = false. - Description: - Useful for standardizing edge regions when searching for duplicates. - */ - ON_SubDComponentRegion ReverseIfMarked() const; - - static int Compare( - const ON_SubDComponentRegion* lhs, - const ON_SubDComponentRegion* rhs - ); - - static int CompareTypeIdMarkRegion( - const ON_SubDComponentRegion* lhs, - const ON_SubDComponentRegion* rhs - ); - - void SetLevel0Component( - ON_SubDComponentPtr component_ptr - ); - - void SetLevel0Face( - const ON_SubDFace* face - ); - - void SetLevel0EdgePtr( - const ON_SubDEdgePtr edge_ptr - ); - - void SetLevel0Vertex( - const ON_SubDVertex* vertex - ); - - void Push( - unsigned int region_index - ); - - /* - Description: - Get a string of the form fN.a.b.c where N = m_level0_face-m_id, a.b.c = m_region_index[] values. - */ - const wchar_t* ToString( - wchar_t* s, - size_t s_capacity - ) const; - - const ON_wString ToString() const; - - void Pop(); -}; class ON_SUBD_CLASS ON_SubDLimitNurbsFragment { @@ -4095,11 +4458,8 @@ public: // knot vector is uniform and not clamped. For example {-2,-1,0,1,2,3,4}. double m_patch_cv[5][5][3]; - // m_face_region identifies what part of the SubD region is represented by the patches - ON_SubDComponentRegion m_face_region; - - // m_face_region identifies what part of the SubD level0 edges are on the patch boundaries. - ON_SubDComponentRegion m_edge_region[4]; + // m_face_region identifies what part of the SubD is represented by this patch + ON_SubDFaceRegion m_face_region; ON_SubDLimitNurbsFragment::Type m_type = ON_SubDLimitNurbsFragment::Type::Unset; @@ -4731,6 +5091,19 @@ public: class ON_SubDSectorLimitPoint& limit_point ) const; + /* + Description: + If there is a saved limit point, then its location is returned in limit_point[]. + Parameters: + limit_point - [out] + Returns: + ON_SubD::SubDType::Unset: No saved limit point. The input value of limit_point[] is not changed. + Otherwise: The type of saved SubD limit point and its location is returned in limit_point[]. + */ + ON_SubD::SubDType GetSavedLimitPointLocation( + double limit_point[3] + ) const; + /* Description: Save limit point and limit normal for future use. diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index 9a0ec42b..69f5f7f1 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -1800,15 +1800,13 @@ public: bool Send4x4Patch( unsigned int display_density, - const class ON_SubDComponentRegion& face_region, - const class ON_SubDComponentRegion edge_region[], // [4] + const class ON_SubDFaceRegion& face_region, ON_SubDLimitNurbsFragment::BispanType bispan_type ); bool Send5x5Patch( unsigned int display_density, - const class ON_SubDComponentRegion& face_region, - const class ON_SubDComponentRegion edge_region[], // [4] + const class ON_SubDFaceRegion& face_region, const ON_SubDLimitNurbsFragment::BispanType bispan_type[4] ); }; @@ -1900,14 +1898,12 @@ public: ); bool GetLimitMesh( - class ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, const ON_SubDFace* face ); bool GetLimitPatches( - class ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, const ON_SubDFace* face ); @@ -1978,8 +1974,7 @@ private: ) const; bool GetLimitSubMeshOrPatch( - class ON_SubDComponentRegion& face_region, - class ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, class ON_SubDQuadNeighborhood* qft, // may be destroyed unsigned int display_density, unsigned int point_i0, diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index fabc94a3..0ce9722c 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -666,6 +666,21 @@ bool ON_SubDVertex::GetLimitPoint( return rc; } +ON_SubD::SubDType ON_SubDVertex::GetSavedLimitPointLocation( + double limit_point[3] +) const +{ + const ON_SubD::SubDType subd_type = SavedLimitPointType(); + if (ON_SubD::SubDType::Unset != subd_type) + { + limit_point[0] = m_limit_point.m_limitP[0]; + limit_point[1] = m_limit_point.m_limitP[1]; + limit_point[2] = m_limit_point.m_limitP[2]; + } + return subd_type; +} + + static bool SetLimitPointSectorCheck( const ON_SubDVertex* vertex, ON_SubDSectorLimitPoint& limit_point diff --git a/opennurbs_subd_mesh.cpp b/opennurbs_subd_mesh.cpp index fa272625..852852f1 100644 --- a/opennurbs_subd_mesh.cpp +++ b/opennurbs_subd_mesh.cpp @@ -28,28 +28,26 @@ bool ON_SubDFaceRegionBreakpoint( unsigned int level0_face_id, - unsigned short subdivision_count, - const unsigned short* region_index - + const class ON_SubDComponentRegionIndex& region_index ) { #if defined(ON_DEBUG) if ( - 27 != level0_face_id + 11 != level0_face_id ) { return false; } - const unsigned short region_pattern[] = { 0, 0 }; + const unsigned short region_pattern[] = { 3, 3 }; const unsigned short region_pattern_count = (unsigned short)(sizeof(region_pattern) / sizeof(region_pattern[0])); - if (subdivision_count < region_pattern_count) + if (region_index.m_subdivision_count < region_pattern_count) return false; for (unsigned short i = 0; i < region_pattern_count; i++) { - if (region_index[i] != region_pattern[i]) + if (region_index.m_index[i] != region_pattern[i]) return false; } @@ -67,7 +65,7 @@ bool ON_SubDComponentRegionBreakpoint(const ON_SubDComponentRegion* component_re switch (component_region->m_level0_component.ComponentType()) { case ON_SubDComponentPtr::Type::Face: - return ON_SubDFaceRegionBreakpoint(component_region->m_level0_component_id, component_region->m_subdivision_count, component_region->m_region_index); + return ON_SubDFaceRegionBreakpoint(component_region->m_level0_component_id, component_region->m_region_index); break; case ON_SubDComponentPtr::Type::Edge: break; @@ -241,50 +239,189 @@ ON_NurbsSurface* ON_SubDLimitNurbsFragment::GetQuadrantSurface( return surface; } - -// Generates an edge region identifier for "new" subdivision -// edges that run from the face subd point to the edge subd point. -static ON_SubDComponentRegion Internal_NewSubdivisonEdgeRegion( - const ON_SubDComponentRegion& face_region, - unsigned int face_edge_index -) +const ON_SubDComponentRegion Internal_CreateSubdivisionEdgeRegion(unsigned short subdivision_count, bool bReversedEdge ) { - ON_SubDComponentRegion r(face_region); - r.m_level0_component = ON_SubDComponentPtr::CreateNull(ON_SubDComponentPtr::Type::Edge, false); - r.m_level0_component_id |= 0x80000000; - r.Push(face_edge_index); - return r; + return ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, bReversedEdge, subdivision_count, true); } +class Internal_SubQuadTransientComponents +{ +public: + Internal_SubQuadTransientComponents(ON_SubDFaceIterator& fit) + { + // The edge_transient_vertex_id[] array stores ON_2udex + // that are used to record the transient vertex id + // for a level 0 edge subdivision point. + // ON_2udex.i = level 0 edge id + // ON_2udex.j = transient vertex id to use for the edge subdivision vertex + // + // These are required when an edge is shared by 2 N-gons with N != 4 because + // each N-gon must use the same transient vertex id in order for the + // final NURBS surface to be easily and quickly joined into an ON_Brep. + + m_edge_transient_vertex_id_map.Reserve(64); + ON_2udex u; + u.i = 0; + u.j = 0; + for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) + { + const unsigned int edge_count = face->EdgeCount(); + if (edge_count <= 0 || 4 == edge_count) + continue; + for (unsigned int fei = 0; fei < edge_count; fei++) + { + const ON_SubDEdge* e = face->Edge(fei); + if (nullptr == e) + continue; + u.i = e->m_id; + m_edge_transient_vertex_id_map.Append(u); + } + } + + const unsigned int count0 = m_edge_transient_vertex_id_map.UnsignedCount(); + if (count0 <= 0) + return; + + m_edge_transient_vertex_id_map.QuickSort(ON_2udex::DictionaryCompare); + unsigned int count1 = 0; + unsigned int prev_edge_id = 0; + ON_2udex* a = m_edge_transient_vertex_id_map.Array(); + for (unsigned int i = 0; i < count0; i++) + { + u = a[i]; + if (u.i <= prev_edge_id) + continue; // paired edge + prev_edge_id = u.i; + u.j = ON_SubDComponentRegion::NewTransientId(); + a[count1++] = u; + } + m_edge_transient_vertex_id_map.SetCount(count1); + } + + ~Internal_SubQuadTransientComponents() = default; + +public: + static const Internal_SubQuadTransientComponents Empty; + +public: + void Initialize( + const ON_SubDFace* face + ) + { + m_face = nullptr; + m_edge_count = 0; + + if (m_edge_transient_vertex_id_map.UnsignedCount() <= 0) + return; + if (nullptr == face) + return; + const unsigned short edge_count = face->m_edge_count; + if (edge_count < 3 || 4 == edge_count) + return; + + // Used to create edge regions for subdivision edges because their is no "real" + // edge to reference. + // We need a unique id and region so merging and brep joining work correctly. + m_edge_count = edge_count; + m_fei = edge_count-1; + m_face = face; + m_radial_edge_region[2] = ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, false, 1, true); + m_radial_edge_region[1] = m_radial_edge_region[2]; + m_face_center_vertex_id = ON_SubDComponentRegion::NewTransientId(); + m_radial_vertex_id[2] = Internal_EdgeTransientVertexId(); + m_radial_vertex_id[1] = m_radial_vertex_id[2]; + + } + + void NextSubQuad() + { + if (0 == m_edge_count || nullptr == m_face || 0 == m_edge_transient_vertex_id_map.UnsignedCount() ) + return; + m_fei = (m_fei + 1) % m_edge_count; + const bool bCreateSubdivisionEdgeRegion = (m_fei + 1 < m_edge_count); + m_radial_edge_region[0] = m_radial_edge_region[1]; + m_radial_edge_region[1] + = bCreateSubdivisionEdgeRegion + ? ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, false, 1, true) + : m_radial_edge_region[2]; + m_radial_vertex_id[0] = m_radial_vertex_id[1]; + m_radial_vertex_id[1] + = bCreateSubdivisionEdgeRegion + ? Internal_EdgeTransientVertexId() + : m_radial_vertex_id[2]; + } + +public: + const ON_SubDFace* m_face = nullptr; + ON_SubDComponentRegion m_radial_edge_region[3] = {}; + unsigned int m_face_center_vertex_id = 0; + unsigned int m_radial_vertex_id[3] = {}; + unsigned short m_edge_count = 0; + unsigned short m_fei = 0; + + ON_SimpleArray< ON_2udex > m_edge_transient_vertex_id_map; + +private: + Internal_SubQuadTransientComponents() = delete; + Internal_SubQuadTransientComponents(const Internal_SubQuadTransientComponents&) = delete; + Internal_SubQuadTransientComponents& operator= (const Internal_SubQuadTransientComponents&) = delete; + +private: + unsigned int Internal_EdgeTransientVertexId() const + { + const ON_SubDEdge* e = (m_edge_count>0) ? m_face->Edge((m_fei+1)%m_edge_count) : nullptr; + if (nullptr == e) + return 0; + const ON_2udex key(e->m_id, 0); + int i = m_edge_transient_vertex_id_map.BinarySearch(&key, ON_2udex::CompareFirstIndex); + return (i >= 0) ?m_edge_transient_vertex_id_map[i].j : 0; + } +}; + + static void Internal_SetLevel0FaceAndEdgeRegion( const ON_SubDFace* face, unsigned int qi, - ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[4] + const Internal_SubQuadTransientComponents& face_transient_components, + ON_SubDFaceRegion& face_region ) { + face_region = ON_SubDFaceRegion::Empty; const unsigned int N = face->EdgeCount(); - face_region.SetLevel0Face(face); + face_region.m_face_region.SetLevel0Face(face); if ( 4 == N ) { for (unsigned int fei = 0; fei < 4; fei++) - edge_region[fei].SetLevel0EdgePtr(face->EdgePtr(fei)); + face_region.m_edge_region[fei].SetLevel0EdgePtr(face->EdgePtr(fei)); + for (unsigned int fvi = 0; fvi < 4; fvi++) + { + const ON_SubDVertex* v = face->Vertex(fvi); + if (nullptr != v) + face_region.m_vertex_id[fvi] = v->m_id; + } } else if (N >= 3 && qi < N) { face_region.Push(qi); // original N-gon (N != 4) was subdivided into N quads. - edge_region[0] = Internal_NewSubdivisonEdgeRegion(face_region, 0); - edge_region[1].SetLevel0EdgePtr(face->EdgePtr((qi + N - 1) % N)); - edge_region[1].Push(1); - edge_region[2].SetLevel0EdgePtr(face->EdgePtr(qi)); - edge_region[2].Push(0); - edge_region[3] = Internal_NewSubdivisonEdgeRegion(face_region, 3); + + const ON_SubDVertex* v = face->Vertex((qi+1)%N); + if (nullptr != v) + face_region.m_vertex_id[2] = v->m_id; + face_region.m_vertex_id[0] = face_transient_components.m_face_center_vertex_id; + face_region.m_vertex_id[1] = face_transient_components.m_radial_vertex_id[0]; + face_region.m_vertex_id[3] = face_transient_components.m_radial_vertex_id[1]; + + face_region.m_edge_region[0] = face_transient_components.m_radial_edge_region[0]; + face_region.m_edge_region[1].SetLevel0EdgePtr(face->EdgePtr(qi)); + face_region.m_edge_region[1].PushAdjusted(1); + face_region.m_edge_region[2].SetLevel0EdgePtr(face->EdgePtr((qi+1)%N)); + face_region.m_edge_region[2].PushAdjusted(0); + face_region.m_edge_region[3] = face_transient_components.m_radial_edge_region[1]; + face_region.m_edge_region[3].m_level0_component = face_region.m_edge_region[3].m_level0_component.SetMark(); } else { ON_SUBD_ERROR("Unexpected parameters."); - for (unsigned int fei = 0; fei < 4; fei++) - edge_region[fei] = ON_SubDComponentRegion::Empty; } } @@ -311,57 +448,62 @@ const ON_SubDComponentRegion ON_SubDComponentRegion::Create( return r; } - -ON_SubDComponentRegion ON_SubDComponentRegion::Reverse() const +const ON_SubDComponentRegion ON_SubDComponentRegion::CreateSubdivisionRegion( + ON_SubDComponentPtr::Type component_type, + bool bComponentMark, + unsigned short subdivision_count, + bool bAssignTransientId +) { - ON_SubDComponentRegion r(*this); - r.m_level0_component.ToggleMark(); - if (r.m_subdivision_count > 0) + ON_SubDComponentRegion r; + r.m_region_index = ON_SubDComponentRegionIndex::Unset; + r.m_region_index.m_subdivision_count = subdivision_count; + r.m_level0_component = ON_SubDComponentPtr::CreateNull(component_type, bComponentMark); + + if (bAssignTransientId) { - const int c = (int)(sizeof(m_region_index) / sizeof(m_region_index[0])); - int i = (int)(r.m_subdivision_count - 1); - for ( int j = 0; j < i && j < c; ++j,--i) - { - if (i < c) - { - unsigned short x = r.m_region_index[i]; - r.m_region_index[i] = r.m_region_index[j]; - r.m_region_index[j] = x; - } - else - { - r.m_region_index[j] = 0; - } - } - } + r.m_level0_component_id = ON_SubDComponentRegion::NewTransientId(); + } return r; } -ON_SubDComponentRegion ON_SubDComponentRegion::ReverseIfMarked() const -{ - return - 0 != m_level0_component.ComponentMark() - ? Reverse() - : *this; -} +////ON_SubDComponentRegion ON_SubDComponentRegion::Reverse() const +////{ +//// ON_SubDComponentRegion r(*this); +//// r.m_level0_component = r.m_level0_component.ToggleMark(); +//// if (r.m_subdivision_count > 0) +//// { +//// const int c = (int)(sizeof(m_region_index) / sizeof(m_region_index[0])); +//// int i = (int)(r.m_subdivision_count - 1); +//// int j = 0; +//// while (j < c && 0xFFFF == r.m_region_index[j]) +//// j++; +//// for ( /*empty init*/; j < i && j < c; ++j,--i) +//// { +//// if (i < c) +//// { +//// unsigned short x = r.m_region_index[i]; +//// r.m_region_index[i] = r.m_region_index[j]; +//// r.m_region_index[j] = x; +//// } +//// else +//// { +//// r.m_region_index[j] = 0; +//// } +//// } +//// } +//// return r; +////} +//// +////ON_SubDComponentRegion ON_SubDComponentRegion::ReverseIfMarked() const +////{ +//// return +//// 0 != m_level0_component.ComponentMark() +//// ? Reverse() +//// : *this; +////} -int ON_SubDComponentRegion::Compare( - const ON_SubDComponentRegion* lhs, - const ON_SubDComponentRegion* rhs -) -{ - int rc = ON_SubDComponentRegion::CompareTypeIdMarkRegion(lhs, rhs); - if (0 == rc && nullptr != lhs && nullptr != rhs) - { - if (lhs->m_level0_component.m_ptr < rhs->m_level0_component.m_ptr) - return -1; - if (lhs->m_level0_component.m_ptr > rhs->m_level0_component.m_ptr) - return 1; - } - return rc; -} - -int ON_SubDComponentRegion::CompareTypeIdMarkRegion( +int ON_SubDComponentRegion::CompareTypeIdMark( const ON_SubDComponentRegion* lhs, const ON_SubDComponentRegion* rhs ) @@ -386,25 +528,112 @@ int ON_SubDComponentRegion::CompareTypeIdMarkRegion( if (0 != rc) return rc; - const unsigned short region_index_capacity = (unsigned short)(sizeof(m_region_index) / sizeof(m_region_index[0])); - unsigned short subdivision_count0 = (lhs->m_subdivision_count > rhs->m_subdivision_count) ? lhs->m_subdivision_count : rhs->m_subdivision_count; - if (subdivision_count0 > region_index_capacity) - subdivision_count0 = region_index_capacity; - for (unsigned short i = 0; i < subdivision_count0; i++) - { - if (lhs->m_region_index[i] > rhs->m_region_index[i]) - return 1; - if (lhs->m_region_index[i] < rhs->m_region_index[i]) - return -1; - } + return 0; +} + +int ON_SubDComponentRegionIndex::Compare( + const ON_SubDComponentRegionIndex* lhs, + const ON_SubDComponentRegionIndex* rhs + ) +{ + if (lhs == rhs) + return 0; + if (nullptr == rhs) + return 1; + if (nullptr == lhs) + return -1; if (lhs->m_subdivision_count < rhs->m_subdivision_count) return -1; if (lhs->m_subdivision_count > rhs->m_subdivision_count) return 1; + return ON_SubDComponentRegionIndex::CompareMinimumSubregion(lhs, rhs); +} + +int ON_SubDComponentRegionIndex::CompareMinimumSubregion( + const ON_SubDComponentRegionIndex* lhs, + const ON_SubDComponentRegionIndex* rhs + ) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + unsigned short subdivision_count0 = (lhs->m_subdivision_count < rhs->m_subdivision_count) ? lhs->m_subdivision_count : rhs->m_subdivision_count; + if (subdivision_count0 > ON_SubDComponentRegionIndex::IndexCapacity) + subdivision_count0 = ON_SubDComponentRegionIndex::IndexCapacity; + for (unsigned short i = 0; i < subdivision_count0; i++) + { + if (lhs->m_index[i] < rhs->m_index[i]) + return -1; + if (lhs->m_index[i] > rhs->m_index[i]) + return 1; + } + return 0; +} + +int ON_SubDComponentRegion::CompareTypeIdMarkMinimumSubregion( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs +) +{ + if (lhs == rhs) + return 0; + + const int rc = ON_SubDComponentRegion::CompareTypeIdMark(lhs, rhs); + if (0 != rc) + return rc; + + return ON_SubDComponentRegionIndex::CompareMinimumSubregion( &lhs->m_region_index, &rhs->m_region_index); +} + +int ON_SubDComponentRegion::CompareTypeIdMarkSubregion( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs +) +{ + if (lhs == rhs) + return 0; + + int rc = ON_SubDComponentRegion::CompareTypeIdMark(lhs, rhs); + if (0 == rc) + { + rc = ON_SubDComponentRegionIndex::CompareMinimumSubregion(&lhs->m_region_index, &rhs->m_region_index); + if (0 == rc) + { + if (lhs->m_region_index.m_subdivision_count < rhs->m_region_index.m_subdivision_count) + rc = -1; + else if (lhs->m_region_index.m_subdivision_count > rhs->m_region_index.m_subdivision_count) + rc = 1; + } + } + + return rc; +} + + +int ON_SubDComponentRegion::Compare( + const ON_SubDComponentRegion* lhs, + const ON_SubDComponentRegion* rhs +) +{ + if (lhs == rhs) + return 0; + + const int rc = ON_SubDComponentRegion::CompareTypeIdMarkSubregion(lhs, rhs); + if (0 != rc) + return rc; + + if (lhs->m_level0_component.m_ptr < rhs->m_level0_component.m_ptr) + return -1; + if (lhs->m_level0_component.m_ptr > rhs->m_level0_component.m_ptr) + return 1; return 0; } + void ON_SubDComponentRegion::SetLevel0Component( ON_SubDComponentPtr component_ptr ) @@ -420,7 +649,7 @@ void ON_SubDComponentRegion::SetLevel0Component( m_level0_component = ON_SubDComponentPtr::Null; m_level0_component_id = 0; } - m_subdivision_count = 0; + m_region_index = ON_SubDComponentRegionIndex::Zero; } void ON_SubDComponentRegion::SetLevel0Face( @@ -444,36 +673,363 @@ void ON_SubDComponentRegion::SetLevel0Vertex( SetLevel0Component(ON_SubDComponentPtr::Create(vertex)); } - - - -void ON_SubDComponentRegion::Push( - unsigned int face_corner_index +void ON_SubDComponentRegionIndex::Push( + unsigned int region_index ) { - if ( face_corner_index >= 0xFFFFU ) - face_corner_index = 0xFFFFU; - if ( m_subdivision_count < ON_SubDComponentRegion::region_index_capacity ) - m_region_index[m_subdivision_count] = (unsigned short)face_corner_index; - m_subdivision_count++; + if ( region_index > 0xFFFFU ) + region_index = 0xFFFFU; + if ( m_subdivision_count < ON_SubDComponentRegionIndex::IndexCapacity ) + m_index[m_subdivision_count] = (unsigned short)region_index; + ++m_subdivision_count; +} + +void ON_SubDComponentRegionIndex::Pop() +{ + if (m_subdivision_count > 0) + { + m_subdivision_count--; + if ( m_subdivision_count < ON_SubDComponentRegionIndex::IndexCapacity) + m_index[m_subdivision_count] = 0; + } +} + + +void ON_SubDComponentRegion::PushAdjusted( + unsigned int region_index + ) +{ + if ( + ON_SubDComponentPtr::Type::Edge == m_level0_component.ComponentType() + && 0 != m_level0_component.ComponentMark() + && region_index <= 1 + ) + { + region_index = 1 - region_index; + } + PushAbsolute(region_index); +} + +void ON_SubDComponentRegion::PushAbsolute( + unsigned int region_index + ) +{ + m_region_index.Push(region_index); ON_SubDComponentRegionBreakpoint(this); } void ON_SubDComponentRegion::Pop() { - if ( m_subdivision_count > 0 ) - m_subdivision_count--; + m_region_index.Pop(); +} + +unsigned short ON_SubDComponentRegion::SubdivisionCount() const +{ + return m_region_index.m_subdivision_count; +} + + +unsigned short ON_SubDComponentRegionIndex::Index( + unsigned short i +) const +{ + return + (i < m_subdivision_count && i < ON_SubDComponentRegionIndex::IndexCapacity) + ? m_index[i] + : 0xFFFF; +} + +void ON_SubDFaceRegion::Push(unsigned int quadrant_index) +{ + m_face_region.PushAbsolute(quadrant_index); + + if (quadrant_index >= 0 && quadrant_index < 4) + { + m_edge_region[quadrant_index].PushAdjusted(0); // 1st half of this edge relative to face's orientation (adjusted to edge's orientation) + m_edge_region[(quadrant_index + 1) % 4] = ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, true, m_edge_region[quadrant_index].SubdivisionCount(), false); + m_edge_region[(quadrant_index + 2) % 4] = ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, false, m_edge_region[quadrant_index].SubdivisionCount(), false); + m_edge_region[(quadrant_index + 3) % 4].PushAdjusted(1); // 2nd half of this edge relative to face's orientation (adjusted to edge's orientation) + } + + const int surviving_vi + = ((4 != m_level0_edge_count) && (1 == m_face_region.SubdivisionCount())) + ? 2 + : quadrant_index; + m_vertex_id[(surviving_vi+1)%4] = 0; + m_vertex_id[(surviving_vi+2)%4] = 0; + m_vertex_id[(surviving_vi+3)%4] = 0; +} + +bool ON_SubDComponentRegion::IsEmptyRegion() const +{ + return + ON_SubDComponentPtr::Type::Unset == m_level0_component.ComponentType() + && m_level0_component.IsNull() + && 0 == m_level0_component_id + && 0 == SubdivisionCount(); +} + +bool ON_SubDFaceRegion::IsValid( + bool bSilentError +) const +{ + if (m_face_region.IsEmptyRegion()) + { + for (int ei = 0; ei < 4; ei++) + { + if (false == m_edge_region[ei].IsEmptyRegion()) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_face_region is empty and m_edge_region[] is not empty."); + return false; + } + } + for (int vi = 0; vi < 4; vi++) + { + if (0 != m_vertex_id[vi]) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_face_region is empty and m_vertex_id[] is not zero."); + return false; + } + } + return true; + } + + + + const ON_SubDComponentPtr::Type face_type = m_face_region.m_level0_component.ComponentType(); + if (ON_SubDComponentPtr::Type::Face != face_type) + { + if (false == bSilentError) + ON_SUBD_ERROR("Invalid m_face_region."); + return false; + } + + if (false == m_face_region.IsPersistentId()) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_face_region.IsPersistentId() is false"); + return false; + } + + const ON_SubDFace* face = m_face_region.m_level0_component.Face(); + if (nullptr == face ) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_face_region.m_level0_component.Face() is nullptr."); + return false; + } + + if (face->m_id != m_face_region.m_level0_component_id) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected value for m_face_region.m_level0_component_id"); + return false; + } + + const unsigned int edge_count = (nullptr != face) ? face->EdgeCount() : 0; + const bool bIsQuad = (4 == edge_count); + if (false == bIsQuad) + { + if (0 == m_face_region.SubdivisionCount()) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_face_region is not a quad and 0 = m_subdivision_count."); + return false; + } + if (((unsigned int)m_face_region.m_region_index.m_index[0]) >= edge_count) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected value in face_region.m_region_index[0]."); + return false; + } + } + + bool bPeristentVertex[4] = { bIsQuad, bIsQuad, true, bIsQuad }; + bool bPeristentEdge[4] = { bIsQuad, true, true, bIsQuad }; + for (unsigned short i = bIsQuad?0:1; i < m_face_region.SubdivisionCount() && i < ON_SubDComponentRegionIndex::IndexCapacity; i++) + { + const unsigned short r = m_face_region.m_region_index.m_index[i]; + if (r >= 4) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected value in face_region.m_region_index[]."); + return false; + } + bPeristentVertex[(r+1)%4] = false; + bPeristentVertex[(r+2)%4] = false; + bPeristentVertex[(r+3)%4] = false; + bPeristentEdge[(r+1)%4] = false; + bPeristentEdge[(r+2)%4] = false; + if (false == bPeristentVertex[r] && false == bPeristentEdge[r] && false == bPeristentEdge[(r + 3) % 4] ) + break; + } + + for (int ei = 0; ei < 4; ei++) + { + const bool bEmptyEdge = m_edge_region[ei].IsEmptyRegion(); + if (bEmptyEdge) + { + if ( bPeristentEdge[ei]) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected empty edge in m_edge_region[]."); + return false; + } + continue; + } + + const ON_SubDComponentPtr::Type edge_type = m_edge_region[ei].m_level0_component.ComponentType(); + if (ON_SubDComponentPtr::Type::Edge != edge_type) + { + if (false == bSilentError) + ON_SUBD_ERROR("Invalid m_face_region."); + return false; + } + if ( m_edge_region[ei].SubdivisionCount() != m_face_region.SubdivisionCount() ) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_edge_region[].m_subdivision_count != m_face_region.m_subdivision_count."); + return false; + } + + if (bPeristentEdge[ei]) + { + if (false == m_edge_region[ei].IsPersistentId()) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_edge_region[] missing a persistent edge id."); + return false; + } + const ON_SubDEdge* edge = m_edge_region[ei].m_level0_component.Edge(); + if (nullptr == edge) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected value for m_edge_region[].m_level0_component.Edge()"); + return false; + } + if (edge->m_id != m_edge_region[ei].m_level0_component_id) + { + if (false == bSilentError) + ON_SUBD_ERROR("Unexpected value for m_edge_region[].m_level0_component_id"); + return false; + } + } + else + { + if ( false == m_edge_region[ei].IsTransientId() ) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_edge_region[] missing a transient edge id."); + return false; + } + } + } + + + for (int vi = 0; vi < 4; vi++) + { + if (bPeristentVertex[vi]) + { + if (false == ON_SubDComponentRegion::IsPersistentId(m_vertex_id[vi])) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_vertex_id[] missing a persistent vertex id."); + return false; + } + } + else if ( 0 != m_vertex_id[vi] ) + { + if (false == ON_SubDComponentRegion::IsTransientId(m_vertex_id[vi])) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_vertex_id[] missing a transient vertex id."); + return false; + } + } + } + + return true; +} + +bool ON_SubDComponentRegion::IsTransientId() const +{ + return ON_SubDComponentRegion::IsTransientId(m_level0_component_id); +} + +bool ON_SubDComponentRegion::IsPersistentId() const +{ + return ON_SubDComponentRegion::IsPersistentId(m_level0_component_id); +} + +static unsigned int Internal_TransientIdHelper( + bool bReset +) +{ + static unsigned int src = 0; // shoule be a "atomic" unsigned - but not critical + + if (bReset) + { + src = 0; + return 0; + } + + unsigned int transient_id = ++src; + if (0 != (ON_SubDComponentRegion::TransientIdBit & transient_id)) + { + src = 1; + transient_id = 1; // because other threads may have modified src. + } + transient_id |= ON_SubDComponentRegion::TransientIdBit; + return transient_id; +} + +void ON_SubDComponentRegion::ResetTransientId() +{ + Internal_TransientIdHelper(true); +} + +const unsigned int ON_SubDComponentRegion::NewTransientId() +{ + return Internal_TransientIdHelper(false); +} + +bool ON_SubDComponentRegion::IsPersistentId(unsigned int id) +{ + return (0 != id) && (0 == (ON_SubDComponentRegion::TransientIdBit & id)); +} + +unsigned int ON_SubDComponentRegion::TransientId(unsigned int id) +{ + return (0 != (ON_SubDComponentRegion::TransientIdBit & id)) ? ((~ON_SubDComponentRegion::TransientIdBit) & id) : 0; +} + + +bool ON_SubDComponentRegion::IsTransientId(unsigned int id) +{ + return 0 != (ON_SubDComponentRegion::TransientIdBit & id) && 0 != ((~ON_SubDComponentRegion::TransientIdBit) & id); } static wchar_t* Internal_AppendUnsigned( - wchar_t prefix, + //wchar_t prefix1, + //wchar_t prefix2, unsigned int i, wchar_t* s, wchar_t* s1 ) { - if ( 0 != prefix && s < s1) - *s++ = prefix; + //if (0 != prefix1 && s < s1) + // *s++ = prefix1; + //if (0 != prefix2 && s < s1) + // *s++ = prefix2; + const bool bTransientId = (0 != (ON_SubDComponentRegion::TransientIdBit & i) ); + if (bTransientId) + { + if (s < s1) + *s++ = '<'; + i &= ~ON_SubDComponentRegion::TransientIdBit; + } wchar_t buffer[64]; wchar_t* sdigit = buffer; wchar_t* sdigit1 = sdigit + (sizeof(buffer)/sizeof(buffer[0])); @@ -485,14 +1041,20 @@ static wchar_t* Internal_AppendUnsigned( { while ( s < s1 && 0 != (*s = *sdigit--) ) s++; - return s; + break; } } + if (bTransientId) + { + if (s < s1) + *s++ = '>'; + } + if (s <= s1) + *s = 0; return s; } - -const wchar_t* ON_SubDComponentRegion::ToString( +wchar_t* ON_SubDComponentPtr::ToString( wchar_t* s, size_t s_capacity ) const @@ -505,57 +1067,155 @@ const wchar_t* ON_SubDComponentRegion::ToString( *s1 = 0; if (s < s1) { - wchar_t c; - switch (m_level0_component.ComponentType()) + if (0 == m_ptr) { - case ON_SubDComponentPtr::Type::Vertex: - c = 'v'; - break; - case ON_SubDComponentPtr::Type::Edge: - c = 'e'; - break; - case ON_SubDComponentPtr::Type::Face: - c = 'f'; - break; - case ON_SubDComponentPtr::Type::Unset: - c = 0; - break; - default: - c = 0; - break; - } - - if (0 == c) - { - *s++ = '?'; + if (s + 7 < s1) + { + *s++ = 'N'; + *s++ = 'u'; + *s++ = 'l'; + *s++ = 'l'; + *s++ = 'P'; + *s++ = 't'; + *s++ = 'r'; + } } else { - if (m_level0_component_id > 0) - s = Internal_AppendUnsigned(c, m_level0_component_id, s, s1); + wchar_t c; + switch (ComponentType()) + { + case ON_SubDComponentPtr::Type::Vertex: + c = 'v'; + break; + case ON_SubDComponentPtr::Type::Edge: + if ( s+2 < s1 ) + *s++ = (ComponentMark()) ? '-' : '+'; + c = 'e'; + break; + case ON_SubDComponentPtr::Type::Face: + c = 'f'; + break; + case ON_SubDComponentPtr::Type::Unset: + c = 0; + break; + default: + c = 0; + break; + } + + if (0 == c) + { + *s++ = '?'; + } else { *s++ = c; - if ( s < s1 ) - *s++ = '?'; + if (IsNull() && s + 6 < s1) + { + *s++ = '['; + *s++ = 'n'; + *s++ = 'u'; + *s++ = 'l'; + *s++ = 'l'; + *s++ = ']'; + } } } } + if (nullptr != s && s <= s1) + *s = 0; + return s; +}; - for (unsigned short i = 0; i < m_subdivision_count; i++) + +const ON_wString ON_SubDComponentRegionIndex::ToString() const +{ + wchar_t buffer[32]; + if (nullptr != ToString(buffer, sizeof(buffer) / sizeof(buffer[0]))) + return ON_wString(buffer); + return ON_wString::EmptyString; +} + +wchar_t* ON_SubDComponentRegionIndex::ToString( + wchar_t* s, + size_t s_capacity +) const +{ + if (s_capacity <= 0 || nullptr == s) + return nullptr; + + *s = 0; + wchar_t* s1 = s + (s_capacity - 1); + *s1 = 0; + + if (s < s1) { - if (i >= ON_SubDComponentRegion::region_index_capacity) + for (unsigned short i = 0; i < m_subdivision_count && nullptr != s && s < s1; i++) { if (s < s1) *s++ = '.'; - if (s < s1) - *s++ = '_'; - break; + if (i >= ON_SubDComponentRegionIndex::IndexCapacity) + { + // more subdivision levels that m_region_index[] can record. + if (s < s1) + *s++ = '_'; + break; + } + if (0xFFFF == m_index[i]) + { + // This is component was added during a subdivision + // and did not exist at level i. + if (s < s1) + *s++ = 'x'; + } + else + { + // portion of component subdivided at level i + s = Internal_AppendUnsigned(m_index[i], s, s1); + } } - s = Internal_AppendUnsigned('.', m_region_index[i], s, s1); } - if ( nullptr != s && s <= s1) + + if (nullptr != s && s <= s1) + *s = 0; + + return s; +} + + +const ON_wString ON_SubDComponentPtr::ToString() const +{ + wchar_t buffer[32]; + if (nullptr != ToString(buffer, sizeof(buffer) / sizeof(buffer[0]))) + return ON_wString(buffer); + return ON_wString::EmptyString; +} + + +wchar_t* ON_SubDComponentRegion::ToString( + wchar_t* s, + size_t s_capacity +) const +{ + if (s_capacity <= 0 || nullptr == s) + return nullptr; + + *s = 0; + wchar_t* s1 = s + (s_capacity - 1); + *s1 = 0; + if (s < s1) + { + s = m_level0_component.ToString(s, s_capacity); + if (nullptr != s && s < s1) + s = Internal_AppendUnsigned(m_level0_component_id, s, s1); + } + + if (nullptr != s && s < s1) + s = m_region_index.ToString(s, 1 + (s1 - s)); + + if (nullptr != s && s <= s1) *s = 0; return s; @@ -570,6 +1230,107 @@ const ON_wString ON_SubDComponentRegion::ToString() const return ON_wString::EmptyString; } + +ON__UINT32 ON_SubDComponentRegionIndex::ToCompressedRegionIndex() const +{ + return ON_SubDComponentRegionIndex::ToCompressedRegionIndex( m_subdivision_count, m_index); +} + +const ON_SubDComponentRegionIndex ON_SubDComponentRegionIndex::FromCompressedRegionIndex( + ON__UINT32 compressed_region_index +) +{ + ON_SubDComponentRegionIndex ri; + ON_SubDComponentRegionIndex::FromCompressedRegionIndex(compressed_region_index, &ri.m_subdivision_count, ri.m_index); + return ri; +} + +ON__UINT32 ON_SubDComponentRegionIndex::ToCompressedRegionIndex( + unsigned short subdivision_count, + const unsigned short* region_index + ) +{ + ON__UINT32 rc + = (subdivision_count >= 255) + ? 255 + : (ON__UINT32)subdivision_count; + rc <<= 24; + if (nullptr != region_index && subdivision_count > 0) + { + ON__UINT32 idx + = (region_index[0] >= 255) + ? 255 + : (ON__UINT32)region_index[0]; + idx <<= 16; + ON__UINT32 shift = 14; + for (unsigned short i = 1; i < subdivision_count && i < ON_SubDComponentRegionIndex::IndexCapacity && shift <= 14; i++) + { + ON__UINT32 bits = (ON__UINT32)region_index[i]; + if (bits > 3) + bits = 3; + idx |= (bits << shift); + shift -= 2; + } + rc |= idx; + } + return rc; + //ON__UINT32 shift = 27; + //ON__UINT32 rc = (ON__UINT32)subdivision_count; + //rc <<= shift; + // + //const unsigned short count + // = (subdivision_count <= ON_SubDComponentRegion::region_index_capacity) + // ? subdivision_count + // : ON_SubDComponentRegion::region_index_capacity; + //for (unsigned short i = 0; i < count; i++) + //{ + // shift -= 3; + // ON__UINT32 three_bits = (ON__UINT32)(region_index[i] % 0x07); + // if (0 != three_bits) + // rc |= (three_bits << shift); + //} + //return rc; +} + +void ON_SubDComponentRegionIndex::FromCompressedRegionIndex( + ON__UINT32 compressed_region_index, + unsigned short* subdivision_count, + unsigned short region_index[] +) +{ + const ON__UINT32 count = (compressed_region_index >> 24); + if (nullptr != subdivision_count) + *subdivision_count = (unsigned short)count; + if (nullptr != region_index) + { + region_index[0] = (unsigned short)((compressed_region_index & 0x00FF0000) >> 16); + for (unsigned short i = 1; i < ON_SubDComponentRegionIndex::IndexCapacity; i++) + { + region_index[i] = (unsigned short)((compressed_region_index & 0x0000C000) >> 14); + compressed_region_index <<= 2; + } + } + + //ON__UINT32 shift = 27; + //ON__UINT32 count = (region32 >> shift); + //if (nullptr != subdivision_count) + // *subdivision_count = (unsigned short)count; + //if (nullptr != region_index) + //{ + // for (unsigned short i = 0; i < ON_SubDComponentRegion::region_index_capacity; i++) + // region_index[i] = 0; + // unsigned int idx = region32 << (32-shift); + // for (unsigned short i = 0; 0 != idx && i < ON_SubDComponentRegion::region_index_capacity; i++) + // { + // ON__UINT32 three_bits = (idx & 0xE0000000U); + // three_bits >>= 29; + // region_index[i] = (unsigned short)three_bits; + // idx <<= 3; + // } + //} + +} + static ON_ProgressStepCounter CreateFragmentProgressStepCounter( ON_SubDFaceIterator& fit, const ON_SubDDisplayParameters& limit_mesh_parameters @@ -600,6 +1361,71 @@ static ON_ProgressStepCounter CreateFragmentProgressStepCounter( } +wchar_t* ON_SubDFaceRegion::ToString( + wchar_t* s, + size_t s_capacity +) const +{ + if (s_capacity <= 0 || nullptr == s) + return nullptr; + + wchar_t* s1 = s + s_capacity-1; + *s1 = 0; + + s = m_face_region.ToString(s, s_capacity); + if (nullptr != s && s+4 < s1) + { + for (unsigned int i = 0; i < 4 && nullptr != s && s + 4 < s1; i++) + { + *s++ = ON_wString::Space; + *s++ = (0 == i) ? '(' : ','; + if ( ON_SubDComponentPtr::Type::Edge == m_edge_region[i].m_level0_component.ComponentType() ) + { + s = m_edge_region[i].ToString(s, s1 - s); + } + else + { + *s++ = 'e'; + *s++ = '?'; + } + } + if (nullptr != s && s < s1) + *s++ = ')'; + } + + if (nullptr != s && s+4 < s1) + { + for (unsigned int i = 0; i < 4 && nullptr != s && s + 4 < s1; i++) + { + *s++ = ON_wString::Space; + *s++ = (0 == i) ? '(' : ','; + if (0 != m_vertex_id[i]) + { + *s++ = 'v'; + s = Internal_AppendUnsigned(m_vertex_id[i], s, s1); + } + else + { + *s++ = '0'; + } + } + if (nullptr != s && s < s1) + *s++ = ')'; + } + + if (nullptr != s && s <= s1) + *s = 0; + return s; +} + +const ON_wString ON_SubDFaceRegion::ToString() const +{ + wchar_t buffer[256]; + if (nullptr != ToString(buffer, sizeof(buffer) / sizeof(buffer[0]))) + return ON_wString(buffer); + return ON_wString::EmptyString; +} + static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( ON_SubDFaceIterator& fit, const ON_SubDDisplayParameters& limit_mesh_parameters, @@ -624,9 +1450,6 @@ static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( ON_SubDQuadFaceMesher qfm; ON_SubDQuadFaceMesher sub_qfm; ON_SubDFaceNeighborhood fnbd; - ON_SubDComponentRegion face_region; - ON_SubDComponentRegion edge_region[4]; - qfm.m_output = ON_SubDQuadFaceMesher::Output::mesh; @@ -648,6 +1471,8 @@ static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( } } + Internal_SubQuadTransientComponents face_transient_components(fit); + // TODO // // Support multiple threads by adding more fragment, sub_fragment qfm, sub_qfm and fnbd @@ -657,9 +1482,10 @@ static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( const unsigned short unset_face_edge_index = 0xFFFFU; for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) { - face_region.SetLevel0Face(face); + if (face->m_edge_count < 3) + continue; - //const unsigned int initial_subd_level = static_cast(face->m_level); + face_transient_components.Initialize(face); if (4 == face->m_edge_count) { @@ -691,6 +1517,8 @@ static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( continue; quad_faces = fnbd.m_center_vertex1->m_faces; quad_face_count = fnbd.m_center_vertex1->m_face_count; + if (quad_face_count < 2) + continue; if (callback_fragment != &sub_fragment) { if (0 == sub_fragment.m_P_capacity) @@ -714,15 +1542,21 @@ static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( for (unsigned int qi = 0; qi < quad_face_count; qi++) { - Internal_SetLevel0FaceAndEdgeRegion(face, qi, face_region, edge_region); - + face_transient_components.NextSubQuad(); + ON_SubDFaceRegion face_region; + Internal_SetLevel0FaceAndEdgeRegion( + face, + qi, face_transient_components, // qi and subdivsion_edge_region[] are used only when "f" is a level 1 sud quad of "face" + face_region + ); + const ON_SubDFace* f = quad_faces[qi]; if (unset_face_edge_index == callback_fragment->m_face_vertex_index[0]) callback_fragment->m_face_vertex_index[2] = (unsigned short)((qi+1)%quad_face_count); callback_fragment->m_face_fragment_index = (unsigned short)qi; - if (false == qfm.GetLimitMesh(face_region, edge_region, f)) + if (false == qfm.GetLimitMesh(face_region, f)) continue; fragment_count++; @@ -749,7 +1583,7 @@ static unsigned int GetQuadLimitSurfacePatchFragmentsHelper( ON_SubDFaceIterator& fit, const ON_SubDDisplayParameters& limit_mesh_parameters, ON__UINT_PTR fragment_callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), + bool(*begin_face_callback_function)(ON__UINT_PTR ,const ON_SubDFaceRegion&),//, const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), bool(*patch_fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitNurbsFragment*) ) { @@ -768,7 +1602,8 @@ static unsigned int GetQuadLimitSurfacePatchFragmentsHelper( ON_SubDQuadFaceMesher qfm; ON_SubDQuadFaceMesher sub_qfm; ON_SubDFaceNeighborhood fnbd; - ON_SubDComponentRegion face_region; + + Internal_SubQuadTransientComponents face_transient_components(fit); const ON_SubDFace** quad_faces = nullptr; unsigned int quad_face_count = 0; @@ -790,17 +1625,14 @@ static unsigned int GetQuadLimitSurfacePatchFragmentsHelper( patcher.m_fragment_callback_context = fragment_callback_context; patcher.m_fragment_callback_function = patch_fragment_callback_function; - - // TODO - // - // Support multiple threads by adding more fragment, sub_fragment qfm, sub_qfm and fnbd - // resources and managing them. - // const unsigned int subquad_display_density = (display_density > 1) ? (display_density - 1) : 0; for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) { - face_region.SetLevel0Face(face); + if (face->m_edge_count < 3) + continue; + + face_transient_components.Initialize(face); if (4 == face->m_edge_count) { @@ -818,24 +1650,42 @@ static unsigned int GetQuadLimitSurfacePatchFragmentsHelper( quad_faces = fnbd.m_center_vertex1->m_faces; quad_face_count = fnbd.m_center_vertex1->m_face_count; patcher.m_display_density = subquad_display_density; + if (quad_face_count < 2) + continue; } patcher.m_patch_fragment = ON_SubDLimitNurbsFragment::Unset; - patcher.m_patch_fragment.m_face_region = face_region; - qfm.SetDestinationToPatchFragment(patcher); for (unsigned int qi = 0; qi < quad_face_count; qi++) { + face_transient_components.NextSubQuad(); + + // Internal_SetLevel0FaceAndEdgeRegion() sets face_region and patcher.m_patch_fragment.m_edge_region[] + // In the case when face is not a quad and "f" is the level 1 subd quad, + // the regions (face_region and patcher.m_patch_fragment.m_edge_region[]) + // are "Pushed()" so they indicate what portion of "face" and it's original edges are being used. + ON_SubDFaceRegion face_region; + Internal_SetLevel0FaceAndEdgeRegion( + face, + qi, face_transient_components, // qi and subdivsion_edge_region[] are used only when "f" is a level 1 sud quad of "face" + face_region + ); + const ON_SubDFace* f = quad_faces[qi]; if (nullptr != begin_face_callback_function) { - begin_face_callback_function(fragment_callback_context, face, (f != face) ? f : nullptr, qi); + // SubD to NURBS case: + // fragment_callback_context = pointer to Internal_SubDNurbsPatchGetter class + // begin_face_callback_function = Internal_SubDNurbsPatchGetter::BeginFaceCallback() + // Internal_SubDNurbsPatchGetter::BeginFaceCallback() flushes accumulated surface content from previous SUbD face. + // Then it initializes fragment_callback_context for current "face" and, it it exits, sub-quad "f" + begin_face_callback_function(fragment_callback_context, face_region);//, face, (f != face) ? f : nullptr, qi); } - Internal_SetLevel0FaceAndEdgeRegion(face, qi, face_region, patcher.m_patch_fragment.m_edge_region); + patcher.m_patch_fragment.m_face_region = face_region; - if (false == qfm.GetLimitPatches(face_region, patcher.m_patch_fragment.m_edge_region, f)) + if (false == qfm.GetLimitPatches(face_region, f)) continue; fragment_count++; @@ -2489,8 +3339,7 @@ bool ON_SubDQuadFaceMesher::EvaluateSurface( bool ON_SubDQuadFacePatcher::Send4x4Patch( unsigned int display_density, - const class ON_SubDComponentRegion& face_region, - const class ON_SubDComponentRegion edge_region[], + const class ON_SubDFaceRegion& face_region, ON_SubDLimitNurbsFragment::BispanType bispan_type ) { @@ -2499,20 +3348,20 @@ bool ON_SubDQuadFacePatcher::Send4x4Patch( m_patch_fragment.m_bispan_type[1] = ON_SubDLimitNurbsFragment::BispanType::None; m_patch_fragment.m_bispan_type[2] = ON_SubDLimitNurbsFragment::BispanType::None; m_patch_fragment.m_bispan_type[3] = ON_SubDLimitNurbsFragment::BispanType::None; + m_patch_fragment.m_face_region = face_region; - m_patch_fragment.m_edge_region[0] = edge_region[0]; - m_patch_fragment.m_edge_region[1] = edge_region[1]; - m_patch_fragment.m_edge_region[2] = edge_region[2]; - m_patch_fragment.m_edge_region[3] = edge_region[3]; + const bool rc = m_fragment_callback_function(m_fragment_callback_context,&m_patch_fragment); - m_patch_fragment.m_face_region = face_region; // erase any modifications made by the callback + + // erase any region modifications made by the callback + m_patch_fragment.m_face_region = face_region; + return rc; } bool ON_SubDQuadFacePatcher::Send5x5Patch( unsigned int display_density, - const class ON_SubDComponentRegion& face_region, - const class ON_SubDComponentRegion edge_region[], + const class ON_SubDFaceRegion& face_region, const ON_SubDLimitNurbsFragment::BispanType bispan_type[4] ) { @@ -2521,20 +3370,19 @@ bool ON_SubDQuadFacePatcher::Send5x5Patch( m_patch_fragment.m_bispan_type[1] = bispan_type[1]; m_patch_fragment.m_bispan_type[2] = bispan_type[2]; m_patch_fragment.m_bispan_type[3] = bispan_type[3]; + m_patch_fragment.m_face_region = face_region; - m_patch_fragment.m_edge_region[0] = edge_region[0]; - m_patch_fragment.m_edge_region[1] = edge_region[1]; - m_patch_fragment.m_edge_region[2] = edge_region[2]; - m_patch_fragment.m_edge_region[3] = edge_region[3]; + const bool rc = m_fragment_callback_function(m_fragment_callback_context,&m_patch_fragment); - m_patch_fragment.m_face_region = face_region; // erase any modifications made by the callback + + // erase any region modifications made by the callback + m_patch_fragment.m_face_region = face_region; + return rc; } - bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( - ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, ON_SubDQuadNeighborhood* qft, unsigned int display_density, unsigned int point_i0, @@ -2565,7 +3413,8 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( case ON_SubDQuadFaceMesher::Output::patches: if (false == qft->GetLimitSurfaceCV(&m_patcher->m_patch_fragment.m_patch_cv[0][0][0], 5U)) return ON_SUBD_RETURN_ERROR(false); - m_patcher->Send4x4Patch(display_density,face_region,edge_region,ON_SubDLimitNurbsFragment::BispanType::Exact); + //Internal_UpdateFaceRegionCorners(qft, face_region); + m_patcher->Send4x4Patch(display_density,face_region,ON_SubDLimitNurbsFragment::BispanType::Exact); return true; // even if callback returns false break; } @@ -2626,7 +3475,8 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( if ( 4 != subdivide_count + quadrant_count ) return ON_SUBD_RETURN_ERROR(false); m_patcher->m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicQuadrant; - bool bCallbackResult = m_patcher->Send5x5Patch( display_density, face_region, edge_region, pt ); + //Internal_UpdateFaceRegionCorners(qft, face_region); + bool bCallbackResult = m_patcher->Send5x5Patch( display_density, face_region, pt ); if ( false == bCallbackResult) return true; } @@ -2647,6 +3497,8 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( ReturnLocalFixedSizeHeap(fsh); return ON_SUBD_RETURN_ERROR(false); } + //if (ON_SubDQuadFaceMesher::Output::patches == m_output) + // Internal_UpdateFaceRegionCorners(qft, face_region); subdivide_count--; if (0 == subdivide_count) @@ -2662,19 +3514,11 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( unsigned int submesh_point_i0 = point_i0 + ((1==q0fvi || 2==q0fvi) ? count : 0); unsigned int submesh_point_j0 = point_j0 + ((2==q0fvi || 3==q0fvi) ? count : 0); - face_region.Push(q0fvi); - edge_region[q0fvi].Push(0); // 1st half of this edge - ON_SubDComponentRegion saved_edge_region1 = edge_region[(q0fvi + 1) % 4]; - edge_region[(q0fvi + 1) % 4] = ON_SubDComponentRegion::Empty; - ON_SubDComponentRegion saved_edge_region2 = edge_region[(q0fvi + 2) % 4]; - edge_region[(q0fvi + 2) % 4] = ON_SubDComponentRegion::Empty; - edge_region[(q0fvi+3)%4].Push(1); // 2nd half of this edge - const bool rc = GetLimitSubMeshOrPatch(face_region, edge_region, &qft1, display_density-1, submesh_point_i0, submesh_point_j0 ); - face_region.Pop(); - edge_region[q0fvi].Pop(); - edge_region[(q0fvi + 1) % 4] = saved_edge_region1; - edge_region[(q0fvi + 2) % 4] = saved_edge_region2; - edge_region[(q0fvi + 3) % 4].Pop(); + + ON_SubDFaceRegion face_corner_region(face_region); + face_corner_region.Push(q0fvi); + const bool rc = GetLimitSubMeshOrPatch(face_corner_region, &qft1, display_density-1, submesh_point_i0, submesh_point_j0 ); + ReturnLocalFixedSizeHeap(fsh); if ( false == rc ) return ON_SUBD_RETURN_ERROR(false); @@ -2696,13 +3540,13 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( ON_SubDLimitNurbsFragment::BispanType::None }; - ON_SubDComponentRegionBreakpoint(&face_region); + ON_SubDComponentRegionBreakpoint(&face_region.m_face_region); // Harvest whatever patches are available and allow approximate patches to be returned // if an appropriate number of subdivisions have been performed const bool bEnableApproximatePatch = qft->m_extraordinary_corner_vertex_count <= 1 - && face_region.m_subdivision_count >= 2; + && face_region.m_face_region.SubdivisionCount() >= 2; while( bEnableApproximatePatch @@ -2714,9 +3558,16 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( && nullptr != qft->m_center_edges[3] ) { - const unsigned int extraordinary_vertex_index = qft->ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Crease, 4); - if (extraordinary_vertex_index > 3) - break; + bool bIsDart = false; + unsigned int extraordinary_vertex_index = qft->ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Crease, 4); + if (extraordinary_vertex_index > 3 && ON_SubDQuadFaceMesher::Output::patches == m_output) + { + // Test for Dart fixes RH-31322 + extraordinary_vertex_index = qft->ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Dart, 4); + if ( extraordinary_vertex_index > 3) + break; + bIsDart = true; + } const ON_SubDVertex* extraordinary_vertex = qft->CenterVertex(extraordinary_vertex_index); if (nullptr == extraordinary_vertex) break; @@ -2833,32 +3684,6 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( break; } } - ////if ( - //// bHave_qft1 - //// && 1 == qft1.m_boundary_crease_count - //// && nullptr != qft1.m_center_edges[0] - //// && ON_SubD::EdgeTag::Crease == qft1.m_center_edges[0]->m_edge_tag - //// ) - ////{ - //// const ON_2udex crease_dex[3] = { ON_2udex(1,1), ON_2udex(2,1), ON_2udex(3,1) }; - //// const ON_2udex smooth_dex[3] = { ON_2udex(1,2), ON_2udex(2,2), ON_2udex(3,2) }; - //// for (int k = 0; k < 3; k++) - //// { - //// const ON_SubDVertex* crease_vertex = qft1.m_vertex_grid[crease_dex[k].i][crease_dex[k].j]; - //// if (nullptr == crease_vertex) - //// continue; - //// if ( ON_SubD::VertexTag::Crease != crease_vertex->m_vertex_tag) - //// continue; - //// const ON_SubDVertex* smooth_vertex = qft1.m_vertex_grid[smooth_dex[k].i][smooth_dex[k].j]; - //// if (nullptr == smooth_vertex) - //// continue; - //// if ( ON_SubD::VertexTag::Smooth != smooth_vertex->m_vertex_tag) - //// continue; - //// P[k][0] = 2.0*crease_vertex->m_P[0] - smooth_vertex->m_P[0]; - //// P[k][1] = 2.0*crease_vertex->m_P[1] - smooth_vertex->m_P[1]; - //// P[k][2] = 2.0*crease_vertex->m_P[2] - smooth_vertex->m_P[2]; - //// } - ////} break; } } @@ -2935,7 +3760,7 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( if (quadrant_count > 0) { m_patcher->m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicQuadrant; - bool bCallbackResult = m_patcher->Send5x5Patch(display_density, face_region, edge_region, pt); + bool bCallbackResult = m_patcher->Send5x5Patch(display_density, face_region, pt); if (false == bCallbackResult) return true; @@ -2979,8 +3804,7 @@ bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( } bool ON_SubDQuadFaceMesher::GetLimitMesh( - class ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, const ON_SubDFace* face ) { @@ -3017,13 +3841,12 @@ bool ON_SubDQuadFaceMesher::GetLimitMesh( m_count = count; UnsetMeshPoints(); - return GetLimitSubMeshOrPatch(face_region,edge_region,&qft,m_display_density,0,0); + return GetLimitSubMeshOrPatch(face_region,&qft,m_display_density,0,0); } bool ON_SubDQuadFaceMesher::GetLimitPatches( - class ON_SubDComponentRegion& face_region, - ON_SubDComponentRegion edge_region[], // [4] + const ON_SubDFaceRegion& face_region, const ON_SubDFace* face ) { @@ -3050,7 +3873,7 @@ bool ON_SubDQuadFaceMesher::GetLimitPatches( return ON_SUBD_RETURN_ERROR(false); // GetLimitSubMesh is recursive. - return GetLimitSubMeshOrPatch(face_region,edge_region,&qft,m_display_density,0,0); + return GetLimitSubMeshOrPatch(face_region,&qft,m_display_density,0,0); } @@ -3110,7 +3933,7 @@ unsigned int ON_SubD::GetLimitSurfaceMeshInFragments( unsigned int ON_SubD::GetLimitSurfaceNurbsFragments( const class ON_SubDDisplayParameters& limit_mesh_parameters, ON__UINT_PTR fragment_callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), + bool(*begin_face_callback_function)(ON__UINT_PTR ,const ON_SubDFaceRegion&),//, const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), bool(*fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitNurbsFragment*) ) const { @@ -3255,7 +4078,7 @@ public: public: // m_face_region identifies what part of the SubD level0 face is or will be modeled by m_surface. - ON_SubDComponentRegion m_face_region; + ON_SubDFaceRegion m_face_region; // knot vector is uniform and not clamped. ON_NurbsSurface* m_surface = nullptr; @@ -3638,11 +4461,11 @@ bool ON_SubDLimitSurfaceFragment::SetSurfaceFromQuadrants( return true; ON_NurbsSurface* s[4] = { 0 }; - for (unsigned int quadrant_index = 0; quadrant_index < 4; quadrant_index++) { if (nullptr == m_quadrants[quadrant_index]) return false; + if (nullptr == m_quadrants[quadrant_index]->m_surface) return false; s[quadrant_index] = m_quadrants[quadrant_index]->m_surface; @@ -3699,51 +4522,52 @@ bool ON_SubDLimitSurfaceFragment::SetSurfaceFromQuadrants( const ON_SubDLimitSurfaceFragment ON_SubDLimitSurfaceFragment::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDLimitSurfaceFragment); - -class Internal_SubDNurbsFragmentGetter +class Internal_SubDNurbsPatchGetter { public: - Internal_SubDNurbsFragmentGetter( + Internal_SubDNurbsPatchGetter( const ON_SubD& subd, unsigned int patch_density, ON_SubD::NurbsSurfaceType nurbs_surface_type, - const wchar_t* sUserStringPatchIdKey, - ON_SimpleArray& output_surfaces + ON__UINT_PTR nurbs_callback_context, + bool(*nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) ) - : m_subd(subd) - , m_patch_density(patch_density) - , m_nurbs_surface_type(ON_SubD::NurbsSurfaceType::Unset == nurbs_surface_type ? ON_SubD::NurbsSurfaceType::Medium : nurbs_surface_type) - , m_sUserStringPatchIdKey((nullptr != sUserStringPatchIdKey && sUserStringPatchIdKey[0] > ON_wString::Space) ? sUserStringPatchIdKey : nullptr) - , m_output_surfaces(output_surfaces) + : m_subd(subd) + , m_patch_density(patch_density) + , m_nurbs_surface_type(ON_SubD::NurbsSurfaceType::Unset == nurbs_surface_type ? ON_SubD::NurbsSurfaceType::Medium : nurbs_surface_type) + , m_nurbs_callback_context(nurbs_callback_context) + , m_nurbs_callback_function(nurbs_callback_function) {} const ON_SubD& m_subd; const unsigned int m_patch_density = 2; const ON_SubD::NurbsSurfaceType m_nurbs_surface_type = ON_SubD::NurbsSurfaceType::Unset; - const wchar_t* m_sUserStringPatchIdKey = nullptr; - // m_fragments_face_region identifies the current region being accumulated in m_fragments[] - // and is set in Internal_SubDNurbsFragmentGetter::BeginFaceCallback(). + // m_current_face_region identifies the current region being accumulated in m_fragment_tree + // and is set in Internal_SubDNurbsPatchGetter::BeginFaceCallback(). // If the level 0 face is a quad, then m_fragments_face_region.m_subdivision_count = 0; // If the level 0 face is an N-gon (N != 4), then m_fragments_face_region.m_subdivision_count = 1. - ON_SubDComponentRegion m_fragments_face_region; + ON_SubDFaceRegion m_current_face_region; enum : unsigned int { fragments_acculator_capacity = 5 }; ON_SubDLimitSurfaceFragment* m_fragment_tree = nullptr; - ON_SubDLimitSurfaceFragment* FragmentLeaf(const ON_SubDComponentRegion& face_region); + ON_SubDLimitSurfaceFragment* FragmentLeaf( + const ON_SubDFaceRegion& face_region + ); void AddOutputSurface( - const ON_SubDComponentRegion& face_region, + const ON_SubDFaceRegion& face_region, ON_NurbsSurface* output_surface ); - ON_SimpleArray& m_output_surfaces; - + ON__UINT_PTR m_nurbs_callback_context = 0; + bool(*m_nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) = nullptr; + unsigned int m_bicubic_span_count = 0; void AddPatch( @@ -3757,19 +4581,35 @@ public: ); static bool BeginFaceCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsFragmentGetter* - const class ON_SubDFace* level0_face, - const class ON_SubDFace* level1_face, - unsigned int level1_face_region_index + ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* + const ON_SubDFaceRegion& face_region + //, + //const class ON_SubDFace* level0_face_OBSOLETE, + //const class ON_SubDFace* level1_face_OBSOLETE, + //unsigned int level1_face_region_index_OBSOLETE ); static bool GetLimitSurfaceInPatchesCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsFragmentGetter* + ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* const ON_SubDLimitNurbsFragment* patch_fragment ); + static bool AddToSurfaceArrayCallback1( + ON__UINT_PTR context, // ON_SimpleArray* + const ON_SubDFaceRegion& face_region, + class ON_NurbsSurface* nurbs_surface + ); + + static bool AddToSurfaceArrayCallback2( + ON__UINT_PTR context, // ON_SimpleArray* + const ON_SubDFaceRegion& face_region, + class ON_NurbsSurface* nurbs_surface + ); + private: + unsigned int m_new_subdivision_edge_id = 0; + wchar_t* AppendUnsigned( wchar_t prefix, unsigned int i, @@ -3777,50 +4617,37 @@ private: wchar_t* send ); private: - Internal_SubDNurbsFragmentGetter() = delete; - Internal_SubDNurbsFragmentGetter(const Internal_SubDNurbsFragmentGetter&) = delete; - Internal_SubDNurbsFragmentGetter& operator=(const Internal_SubDNurbsFragmentGetter&) = delete; + Internal_SubDNurbsPatchGetter() = delete; + Internal_SubDNurbsPatchGetter(const Internal_SubDNurbsPatchGetter&) = delete; + Internal_SubDNurbsPatchGetter& operator=(const Internal_SubDNurbsPatchGetter&) = delete; }; -bool Internal_SubDNurbsFragmentGetter::BeginFaceCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsFragmentGetter* - const class ON_SubDFace* level0_face, - const class ON_SubDFace* level1_face, - unsigned int level1_face_region_index +bool Internal_SubDNurbsPatchGetter::BeginFaceCallback( + ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* + const ON_SubDFaceRegion& face_region ) { - Internal_SubDNurbsFragmentGetter* p = (Internal_SubDNurbsFragmentGetter*)context; + Internal_SubDNurbsPatchGetter* p = (Internal_SubDNurbsPatchGetter*)context; if (nullptr == p) return true; - // Flush accumulated fratments + // Flush accumulated fragments from previous face p->ConvertFragmentsToSurfaces(); - p->m_fragments_face_region = ON_SubDComponentRegion::Empty; - if (0 != level0_face ) - { - p->m_fragments_face_region = ON_SubDComponentRegion::Create(level0_face); - const class ON_SubDFace* quad_face = level0_face; - if (nullptr != level1_face && level0_face != level1_face) - { - p->m_fragments_face_region.m_subdivision_count = 1; - p->m_fragments_face_region.m_region_index[0] = (unsigned short)level1_face_region_index; - quad_face = level1_face; - } - } + p->m_current_face_region = face_region; return true; } -bool Internal_SubDNurbsFragmentGetter::GetLimitSurfaceInPatchesCallback( +bool Internal_SubDNurbsPatchGetter::GetLimitSurfaceInPatchesCallback( ON__UINT_PTR context, const ON_SubDLimitNurbsFragment* patch_fragment ) { - ((Internal_SubDNurbsFragmentGetter*)context)->AddPatch(patch_fragment); + ((Internal_SubDNurbsPatchGetter*)context)->AddPatch(patch_fragment); return true; } -wchar_t* Internal_SubDNurbsFragmentGetter::AppendUnsigned( +wchar_t* Internal_SubDNurbsPatchGetter::AppendUnsigned( wchar_t prefix, unsigned int i, wchar_t* s, @@ -3872,66 +4699,69 @@ ON_SubDLimitSurfaceFragment* ON_SubDLimitSurfaceFragment::Quadrant(unsigned int } -ON_SubDLimitSurfaceFragment* Internal_SubDNurbsFragmentGetter::FragmentLeaf( - const ON_SubDComponentRegion& patch_face_region +ON_SubDLimitSurfaceFragment* Internal_SubDNurbsPatchGetter::FragmentLeaf( + const ON_SubDFaceRegion& patch_face_region ) { - if (m_fragments_face_region.m_level0_component_id == 0) + const ON_SubDComponentRegion& fragment_fr = m_current_face_region.m_face_region; + if (fragment_fr.m_level0_component_id == 0) { ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id not set."); return nullptr; } - if (m_fragments_face_region.m_level0_component_id != patch_face_region.m_level0_component_id) + + const ON_SubDComponentRegion& patch_fr = patch_face_region.m_face_region; + if (fragment_fr.m_level0_component_id != patch_fr.m_level0_component_id) { ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id != patch_face_region.m_level0_component_id"); return nullptr; } - if (patch_face_region.m_subdivision_count < m_fragments_face_region.m_subdivision_count) + if (patch_fr.SubdivisionCount() < fragment_fr.SubdivisionCount()) { - ON_SUBD_ERROR("patch_face_region.m_subdivision_count < m_fragments_face_region.m_subdivision_count"); + ON_SUBD_ERROR("patch_face_region.SubdivisionCount() < m_fragments_face_region.SubdivisionCount()"); return nullptr; } - if (patch_face_region.m_subdivision_count > ON_SubDComponentRegion::region_index_capacity) + if (patch_fr.SubdivisionCount() > ON_SubDComponentRegionIndex::IndexCapacity) { // unreasonable number of subdivisions - ON_SUBD_ERROR("patch_face_region.m_subdivision_count > ON_SubDComponentRegion::region_index_capacity"); + ON_SUBD_ERROR("patch_face_region.SubdivisionCount() > ON_SubDComponentRegionIndex::IndexCapacity"); return nullptr; } - for ( unsigned short i = 0; i < m_fragments_face_region.m_subdivision_count; i++ ) + for ( unsigned short i = 0; i < fragment_fr.SubdivisionCount(); i++ ) { - if (m_fragments_face_region.m_region_index[i] != patch_face_region.m_region_index[i]) + if (fragment_fr.m_region_index.m_index[i] != patch_fr.m_region_index.m_index[i]) { ON_SUBD_ERROR("m_fragments_face_region.m_region_index[] differs from patch_face_region"); return nullptr; } } - ON_SubDComponentRegion r = m_fragments_face_region; if (nullptr == m_fragment_tree) { m_fragment_tree = ON_SubDLimitSurfaceFragment::AllocateSurfaceFragment(); - m_fragment_tree->m_face_region = r; + m_fragment_tree->m_face_region = m_current_face_region; } ON_SubDLimitSurfaceFragment* fragment_leaf = m_fragment_tree; - while (r.m_subdivision_count < patch_face_region.m_subdivision_count) + ON_SubDComponentRegion r = fragment_fr; + while (r.SubdivisionCount() < patch_fr.SubdivisionCount()) { - unsigned short quadrant_dex = patch_face_region.m_region_index[r.m_subdivision_count]; + unsigned short quadrant_dex = patch_fr.m_region_index.Index(r.SubdivisionCount()); if (quadrant_dex >= 4) { ON_SUBD_ERROR("patch_face_region.m_region_index[] value >= 4."); return nullptr; } - r.Push(quadrant_dex); // increments r.m_subdivision_count + r.PushAbsolute(quadrant_dex); // increments r.m_subdivision_count fragment_leaf = fragment_leaf->Quadrant(quadrant_dex, true); if (nullptr == fragment_leaf) { ON_SUBD_ERROR("fragment tree allocation error."); return nullptr; } - if (0 != ON_SubDComponentRegion::CompareTypeIdMarkRegion(&r, &fragment_leaf->m_face_region)) + if (0 != ON_SubDComponentRegion::CompareTypeIdMarkSubregion(&r, &fragment_leaf->m_face_region.m_face_region)) { ON_SUBD_ERROR("corrupt fragment tree."); return nullptr; @@ -3942,26 +4772,30 @@ ON_SubDLimitSurfaceFragment* Internal_SubDNurbsFragmentGetter::FragmentLeaf( } -void Internal_SubDNurbsFragmentGetter::AddPatch( +void Internal_SubDNurbsPatchGetter::AddPatch( const ON_SubDLimitNurbsFragment* patch_fragment ) { if (nullptr == patch_fragment) return; + // face_region identifies what part of the SubD region is represented by patch_fragment + const ON_SubDFaceRegion face_region = patch_fragment->m_face_region; + ON_SubDLimitNurbsFragment local_patch_fragment(*patch_fragment); patch_fragment = &local_patch_fragment; + bool rc = false; for (;;) { - if (m_fragments_face_region.m_level0_component_id == 0) + if (m_current_face_region.m_face_region.m_level0_component_id == 0) { ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id == 0"); break; } - if (patch_fragment->m_face_region.m_level0_component_id == 0) + if (patch_fragment->m_face_region.m_face_region.m_level0_component_id == 0) { ON_SUBD_ERROR("patch_fragment->m_face_region.m_level0_component_id == 0"); break; @@ -3989,7 +4823,7 @@ void Internal_SubDNurbsFragmentGetter::AddPatch( if (nullptr == fragment_leaf) { ON_SUBD_ERROR("Unable to get surface holder for patch_fragment->m_face_region."); - break; +break; } if (nullptr != fragment_leaf->m_surface) @@ -4014,7 +4848,7 @@ void Internal_SubDNurbsFragmentGetter::AddPatch( { if (ON_SubDLimitNurbsFragment::BispanType::None == patch_fragment->m_bispan_type[quadrant_index]) continue; - if (false == fragment_leaf->SetQuadrantSurface(patch_fragment->GetQuadrantSurface(quadrant_index,nullptr),quadrant_index)) + if (false == fragment_leaf->SetQuadrantSurface(patch_fragment->GetQuadrantSurface(quadrant_index, nullptr), quadrant_index)) { ON_SUBD_ERROR("Failed to set quadrant surface."); continue; @@ -4038,26 +4872,16 @@ void Internal_SubDNurbsFragmentGetter::AddPatch( ConvertFragmentsToSurfaces(); // Convert this patch. Patches should all set to none if we are merging patches.) + // Restore face and edge region information + local_patch_fragment.m_face_region = face_region; + ConvertPatchToSurfaces(*patch_fragment); return; } -class QWERTY +void Internal_SubDNurbsPatchGetter::ConvertFragmentsToSurfaces() { -public: - ON_NurbsSurface * m_surface = nullptr; - - // m_face_region identifies what part of the SubD level0 face is represented by the patches - ON_SubDComponentRegion m_face_region; - - // m_face_region identifies what part of the SubD level0 edges are on the patch boundaries. - ON_SubDComponentRegion m_edge_region[4]; -}; - -void Internal_SubDNurbsFragmentGetter::ConvertFragmentsToSurfaces() -{ - // Debugging and emergancy output if (nullptr == m_fragment_tree) return; ON_SimpleArray a(64); @@ -4075,7 +4899,7 @@ void Internal_SubDNurbsFragmentGetter::ConvertFragmentsToSurfaces() if (f->m_surface) { AddOutputSurface(f->m_face_region, f->m_surface); - f->m_surface = nullptr; + f->m_surface = nullptr; // receiver must manage f->m_surface. } for (unsigned int j = 0; j < 4; j++) { @@ -4092,12 +4916,12 @@ void Internal_SubDNurbsFragmentGetter::ConvertFragmentsToSurfaces() a[a_count++] = a[k++]; a.SetCount(a_count); } - m_fragments_face_region = ON_SubDComponentRegion::Empty; + m_current_face_region = ON_SubDFaceRegion::Empty; } -void Internal_SubDNurbsFragmentGetter::AddOutputSurface( - const ON_SubDComponentRegion& face_region, +void Internal_SubDNurbsPatchGetter::AddOutputSurface( + const ON_SubDFaceRegion& face_region, ON_NurbsSurface* output_surface ) { @@ -4111,19 +4935,10 @@ void Internal_SubDNurbsFragmentGetter::AddOutputSurface( Internal_CheckNurbsSurfaceCVs(*output_surface); } - if (nullptr != m_sUserStringPatchIdKey && 0 != m_sUserStringPatchIdKey[0]) - { - wchar_t sFaceRegion[64]; - sFaceRegion[0] = 0; - sFaceRegion[0] = 0; - face_region.ToString(sFaceRegion, sizeof(sFaceRegion) / sizeof(sFaceRegion[0])); - output_surface->SetUserString( m_sUserStringPatchIdKey, sFaceRegion); - } - - m_output_surfaces.Append(output_surface); + m_nurbs_callback_function(m_nurbs_callback_context, face_region, output_surface); } -void Internal_SubDNurbsFragmentGetter::ConvertPatchToSurfaces( +void Internal_SubDNurbsPatchGetter::ConvertPatchToSurfaces( const ON_SubDLimitNurbsFragment& patch_fragment ) { @@ -4132,8 +4947,9 @@ void Internal_SubDNurbsFragmentGetter::ConvertPatchToSurfaces( if (bispan_count <= 0) return; - ON_SubDComponentRegion face_region = patch_fragment.m_face_region; unsigned int maximum_bispan_count = patch_fragment.MaximumBispanCount(); + ON_SubDComponentRegion central_edge_regions[4] = {}; + ON_SubDFaceRegion local_face_region = patch_fragment.m_face_region; for (unsigned int quadrant_index = 0; quadrant_index < maximum_bispan_count; quadrant_index++) { if (ON_SubDLimitNurbsFragment::BispanType::None == patch_fragment.m_bispan_type[quadrant_index]) @@ -4142,10 +4958,26 @@ void Internal_SubDNurbsFragmentGetter::ConvertPatchToSurfaces( if (nullptr == output_surface) continue; if (maximum_bispan_count > 1) - face_region.Push(quadrant_index); - AddOutputSurface(face_region, output_surface); + { + // region information must be subdivided + local_face_region.Push(quadrant_index); + const unsigned short central_edge_region_subdivision_count = local_face_region.m_face_region.SubdivisionCount(); + + if (0 == central_edge_regions[quadrant_index].m_level0_component_id) + central_edge_regions[quadrant_index] = Internal_CreateSubdivisionEdgeRegion(central_edge_region_subdivision_count,false); + local_face_region.m_edge_region[(quadrant_index + 1) % 4] = central_edge_regions[quadrant_index]; + local_face_region.m_edge_region[(quadrant_index + 1) % 4].m_level0_component = central_edge_regions[quadrant_index].m_level0_component.ToggleMark(); + + const unsigned int cdex3 = (quadrant_index + 3) % 4; + if (0 == central_edge_regions[cdex3].m_level0_component_id) + central_edge_regions[cdex3] = Internal_CreateSubdivisionEdgeRegion(central_edge_region_subdivision_count,false); + local_face_region.m_edge_region[(quadrant_index+2)%4] = central_edge_regions[cdex3]; + } + + AddOutputSurface(local_face_region, output_surface); + if (maximum_bispan_count > 1) - face_region.Pop(); + local_face_region = patch_fragment.m_face_region; } } @@ -4154,33 +4986,23 @@ unsigned int ON_SubD::GetLimitSurfaceNurbs( const class ON_SubDDisplayParameters& display_parameters, ON_SubD::NurbsSurfaceType nurbs_surface_type, ON__UINT_PTR callback_context, - bool(*nurbs_callback_function)(ON__UINT_PTR, const ON_SubDComponentRegion&, const ON_SubDComponentRegion*, class ON_NurbsSurface*) + bool(*nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) ) const { // TODO: Restructure the code to support this callback function. - return 0; -} - -unsigned int ON_SubD::GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - const wchar_t* sUserStringPatchKey, - ON_SimpleArray< ON_NurbsSurface* >& patches - ) const -{ - Internal_SubDNurbsFragmentGetter patch_getter( + Internal_SubDNurbsPatchGetter patch_getter( *this, display_parameters.m_display_density, nurbs_surface_type, - sUserStringPatchKey, - patches + callback_context, + nurbs_callback_function ); GetLimitSurfaceNurbsFragments( display_parameters, (ON__UINT_PTR)&patch_getter, - Internal_SubDNurbsFragmentGetter::BeginFaceCallback, - Internal_SubDNurbsFragmentGetter::GetLimitSurfaceInPatchesCallback + Internal_SubDNurbsPatchGetter::BeginFaceCallback, + Internal_SubDNurbsPatchGetter::GetLimitSurfaceInPatchesCallback ); // Flush the final batch of patches. @@ -4189,3 +5011,477 @@ unsigned int ON_SubD::GetLimitSurfaceNurbs( return patch_getter.m_bicubic_span_count; } +bool Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback1( + ON__UINT_PTR context, // ON_SimpleArray* + const ON_SubDFaceRegion& face_region, + class ON_NurbsSurface* nurbs_surface +) +{ + if (nullptr != nurbs_surface && 0 != context) + { + ON_wString region_id = face_region.ToString(); + nurbs_surface->SetUserString(L"SubDRegionId", region_id); + ((ON_SimpleArray< ON_NurbsSurface* >*)context)->Append(nurbs_surface); + } + return true; +} + +bool Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2( + ON__UINT_PTR context, // ON_SimpleArray* + const ON_SubDFaceRegion& face_region, + class ON_NurbsSurface* nurbs_surface +) +{ + if (nullptr != nurbs_surface && 0 != context) + { + ON_SubDFaceRegionAndNurbs& e = ((ON_SimpleArray< ON_SubDFaceRegionAndNurbs >*)context)->AppendNew(); + e.m_face_region = face_region; + e.m_nurbs_surface = nurbs_surface; + } + return true; +} + +unsigned int ON_SubD::GetLimitSurfaceNurbs( + const class ON_SubDDisplayParameters& display_parameters, + ON_SubD::NurbsSurfaceType nurbs_surface_type, + ON_SimpleArray< ON_NurbsSurface* >& patches + ) const +{ + return GetLimitSurfaceNurbs(display_parameters, nurbs_surface_type, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback1); +} + +unsigned int ON_SubD::GetLimitSurfaceNurbs( + const class ON_SubDDisplayParameters& display_parameters, + ON_SubD::NurbsSurfaceType nurbs_surface_type, + ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches + ) const +{ + return GetLimitSurfaceNurbs(display_parameters, nurbs_surface_type, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2); +} + + +static bool Internal_ToBrep( + ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches, + ON_Brep& brep +); + +ON_Brep* ON_SubD::GetLimitSurfaceNurbs( + const class ON_SubDDisplayParameters& display_parameters, + ON_Brep* destination_brep + ) const +{ + if (nullptr != destination_brep) + destination_brep->Destroy(); + ON_SimpleArray< ON_SubDFaceRegionAndNurbs > patches; + GetLimitSurfaceNurbs(display_parameters, ON_SubD::NurbsSurfaceType::Large, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2); + const unsigned int face_count = patches.UnsignedCount(); + if (face_count <= 0) + return nullptr; + + ON_Brep* brep + = (nullptr != destination_brep) + ? destination_brep + : ON_Brep::New(); + if (false == Internal_ToBrep(patches, *brep)) + { + if (brep != destination_brep) + delete brep; + brep = nullptr; + } + return brep; +} + +static bool Internal_ToBrep( + ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches, + ON_Brep& brep +) +{ + const unsigned int face_count = patches.UnsignedCount(); + if (face_count <= 0) + return false; + + const unsigned int split_edge_mark = 0xFFFFFFFF; + const unsigned int bad_edge_mark = 0xFFFFFFFE; + const unsigned int bad_vertex_mark = 0xFFFFFFFD; + + + ON_SimpleArray vertex_map(3 * face_count + 32); + ON_SimpleArray edge_map(4 * face_count + 32); + ON_2udex vm(0, 0); + ON_3udex em(0, 0, 0); + + // Begin working on building vertex_map[] and edge_map[] + for (unsigned int fi = 0; fi < face_count; fi++) + { + const ON_SubDFaceRegionAndNurbs& r = patches[fi]; + ON_NurbsSurface* nurbs_surface = r.m_nurbs_surface; + if (nullptr == nurbs_surface) + continue; + + for (int i = 0; i < 4; i++) + { + vm.i = r.m_face_region.m_vertex_id[i]; + vm.j = fi; + if ( 0 != vm.i) + vertex_map.Append(vm); + + em.i = r.m_face_region.m_edge_region[i].m_level0_component_id; + if (0 != em.i) + { + em.j = r.m_face_region.m_edge_region[i].m_region_index.ToCompressedRegionIndex(); + em.k = fi; + edge_map.Append(em); + } + } + } + + // Remove duplicates from vertex_map[] + vertex_map.QuickSort(ON_2udex::DictionaryCompare); + unsigned int count0 = vertex_map.UnsignedCount(); + unsigned int prev_id = 0; + unsigned int brep_vertex_count = 0; + for (unsigned int i = 0; i < count0; i++) + { + vm = vertex_map[i]; + if (vm.i <= prev_id) + continue; + prev_id = vm.i; + vertex_map[brep_vertex_count++] = vm; + } + vertex_map.SetCount(brep_vertex_count); + + // Create brep vertices and set vertex_map[].j to brep index values so we + // can use vertex_map[] to go from a subd vertex id to a brep vertex index. + brep.m_V.Reserve(brep_vertex_count); + for (unsigned int i = 0; i < brep_vertex_count; i++) + { + ON_3dPoint P(ON_3dPoint::NanPoint); + vm = vertex_map[i]; + const unsigned fi = vm.j; + vertex_map[i].j = bad_vertex_mark; // will be updated below if everything works right + vm.j = bad_vertex_mark; + const ON_SubDFaceRegionAndNurbs& r = patches[fi]; + if (nullptr != r.m_nurbs_surface) + { + for (int j = 0; j < 4; j++) + { + if (vm.i == r.m_face_region.m_vertex_id[j]) + { + const double s = r.m_nurbs_surface->Domain(0)[(1==j||2==j) ? 1 : 0]; + const double t = r.m_nurbs_surface->Domain(1)[(j > 1) ? 1 : 0]; + P = r.m_nurbs_surface->PointAt(s, t); + break; + } + } + } + if (false == P.IsValid()) + { + ON_SUBD_ERROR("Unable to calculate vertex location."); + } + vm.j = brep.m_V.UnsignedCount(); + vertex_map[i].j = vm.j; + brep.NewVertex(P, 0.0); + } + + // Remove duplicates from edge_map[] + + edge_map.QuickSort(ON_3udex::DictionaryCompare); + count0 = edge_map.UnsignedCount(); + unsigned int edge_map_count = 0; + ON_3udex prev_em(0, 0, 0); + for (unsigned int i = 0; i < count0; i++) + { + em = edge_map[i]; + if (prev_em.i == em.i && prev_em.j == em.j) + continue; + prev_em = em; + edge_map[edge_map_count++] = em; + } + // NOTE: final brep edge count <= edge_map_count. + edge_map.SetCount(edge_map_count); + + // create the edges + brep.m_C3.Reserve(edge_map_count); + brep.m_E.Reserve(edge_map_count); + unsigned int brep_edge_count = 0; + for (unsigned int i = 0; i < edge_map_count; i++) + { + em = edge_map[i]; + const unsigned int fi = em.k; + em.k = bad_edge_mark; + edge_map[i].k = bad_edge_mark; // will be update below if we find all the parts. + + if (0 == em.j && i + 2 < edge_map_count && em.i == edge_map[i + 1].i && em.i == edge_map[i + 2].i) + { + // This edge is shared between a subd quad face and N-gon with N != 4. + // The N-gon had to be subdivided one time to create quads and the + // edge will be split on the quad face side. + edge_map[i].k = split_edge_mark; // marker on the quad face edge for this situation + continue; + } + + ON_Curve* edge_curve = nullptr; + const ON_SubDFaceRegionAndNurbs& r = patches[fi]; + const ON_NurbsSurface* nurbs_surface = r.m_nurbs_surface; + unsigned vertex_id[2] = {}; + int brep_vi[2] = { -1,-1 }; + if (nullptr != nurbs_surface) + { + for (int j = 0; j < 4; j++) + { + if (em.i == r.m_face_region.m_edge_region[j].m_level0_component_id) + { + const bool bRevEdge = (0 != r.m_face_region.m_edge_region[j].m_level0_component.ComponentMark()); + vertex_id[bRevEdge?1:0] = r.m_face_region.m_vertex_id[j]; + vertex_id[bRevEdge?0:1] = r.m_face_region.m_vertex_id[(j+1)%4]; + + for (int k = 0; k < 2; k++) + { + vm.i = vertex_id[k]; + vm.j = 0; + int mapdex = vertex_map.BinarySearch(&vm, ON_2udex::CompareFirstIndex); + if (mapdex >= 0) + { + vm = vertex_map[mapdex]; + brep_vi[k] = (int)vm.j; + } + } + const int iso_dir = j % 2; + const double c = nurbs_surface->Domain(1 - iso_dir)[(1==j || 2==j) ? 1 : 0]; + edge_curve = nurbs_surface->IsoCurve(iso_dir, c); + bool bReverseCurve = (j >= 2); + if (bRevEdge) + bReverseCurve = !bReverseCurve; + if (bReverseCurve && nullptr != edge_curve) + edge_curve->Reverse(); + break; + } + } + } + + if (nullptr != edge_curve) + { + for (int k = 0; k < 2; k++) + { + if (brep_vi[k] >= 0) + continue; + ON_SUBD_ERROR("brep edge vertex creation error."); + ON_3dPoint P(ON_3dPoint::NanPoint); + if (nullptr != edge_curve) + P = (0 == k) ? edge_curve->PointAtStart() : edge_curve->PointAtEnd(); + brep_vi[k] = brep.m_V.Count(); + brep.NewVertex(P, 0.0); + } + int c3i = -1; + if (nullptr != edge_curve) + { + c3i = brep.m_C3.Count(); + brep.m_C3.Append(edge_curve); + } + em.k = brep.m_E.UnsignedCount(); + edge_map[i].k = em.k; + brep.NewEdge(brep.m_V[brep_vi[0]], brep.m_V[brep_vi[1]], c3i, nullptr, 0.0); + brep_edge_count++; + } + else + { + ON_SUBD_ERROR("brep edge creation error."); + } + } + + // Now build faces + ON_NurbsCurve trim_curve; + ON_2dPoint trim_curve_cvs[2]; + double trim_curve_knots[2] = { 0.0,1.0 }; + trim_curve.m_dim = 2; + trim_curve.m_is_rat = 0; + trim_curve.m_order = 2; + trim_curve.m_cv_count = 2; + trim_curve.m_cv_stride = 2; + trim_curve.m_cv = &trim_curve_cvs[0].x; + trim_curve.m_knot = trim_curve_knots; + + for (unsigned int fi = 0; fi < face_count; fi++) + { + const ON_SubDFaceRegionAndNurbs& r = patches[fi]; + if (nullptr == r.m_nurbs_surface) + continue; + + const ON_Interval srf_domain[2] = { r.m_nurbs_surface->Domain(0),r.m_nurbs_surface->Domain(1) }; + const ON_2dPoint srf_uv[4] = + { + ON_2dPoint(srf_domain[0][0], srf_domain[1][0]), + ON_2dPoint(srf_domain[0][1], srf_domain[1][0]), + ON_2dPoint(srf_domain[0][1], srf_domain[1][1]), + ON_2dPoint(srf_domain[0][0], srf_domain[1][1]) + }; + + ON_3udex brep_em[8] = {}; + bool trimRev3d[8] = {}; + ON_2udex brep_vm[8] = {}; + + // Fallback if we fail to get complete information + bool bFail = false; + int fail_vid[4] = { -1,-1,-1,-1 }; + int fail_eid[4] = { -1,-1,-1,-1 }; + bool fail_bRev3d[4] = { false,false,false,false }; + + for (int i = 0; i < 4; i++) + { + brep_vm[2 * i] = ON_2udex::Zero; + brep_vm[2 * i + 1] = ON_2udex::Zero; + brep_em[2 * i] = ON_3udex::Zero; + brep_em[2 * i + 1] = ON_3udex::Zero; + + vm.i = r.m_face_region.m_vertex_id[i]; + vm.j = 0; + int k = vertex_map.BinarySearch(&vm, ON_2udex::CompareFirstIndex); + if (k < 0) + { + bFail = true; + } + else + { + vm = vertex_map[k]; + if (vm.j < bad_vertex_mark) + { + brep_vm[2 * i] = vm; + fail_vid[i] = vm.j; + } + else + bFail = true; + } + + const bool bRev3d = (0 != r.m_face_region.m_edge_region[i].m_level0_component.ComponentMark()) ? true : false; + trimRev3d[2 * i] = bRev3d; + em.i = r.m_face_region.m_edge_region[i].m_level0_component_id; + em.j = r.m_face_region.m_edge_region[i].m_region_index.ToCompressedRegionIndex(); + em.k = 0; + k = edge_map.BinarySearch(&em, ON_3udex::CompareFirstAndSecondIndex); + if (k < 0) + { + bFail = true; + } + else + { + em = edge_map[k]; + if (split_edge_mark == em.k) + { + if (0 != em.j || (unsigned)(k + 2) >= edge_map_count || em.i != edge_map[k+1].i || em.i != edge_map[k+2].i ) + bFail = true; + else + { + trimRev3d[2 * i+1] = bRev3d; + if (bRev3d) + { + brep_em[2 * i] = edge_map[k + 2]; + brep_em[2 * i + 1] = edge_map[k + 1]; + } + else + { + brep_em[2 * i] = edge_map[k + 1]; + brep_em[2 * i + 1] = edge_map[k + 2]; + } + } + } + else if ( em.k < bad_edge_mark ) + { + brep_em[2 * i] = em; + fail_eid[i] = em.k; + fail_bRev3d[i] = bRev3d; + } + else + { + bFail = true; + } + } + } + + if (bFail) + { + // Use simple face maker and end up with interior naked edges + brep.NewFace( + r.m_nurbs_surface, + fail_vid, + fail_eid, + fail_bRev3d + ); + } + else + { + const int si = brep.m_S.Count(); + brep.m_S.Append(r.m_nurbs_surface); + ON_BrepFace& brep_face = brep.NewFace(si); + ON_BrepLoop& brep_loop = brep.NewLoop(ON_BrepLoop::TYPE::outer, brep_face); + for (int i = 0; i < 4; i++) + { + trim_curve_cvs[0] = srf_uv[i]; + trim_curve_cvs[1] = srf_uv[(i+1)%4]; + + if ( + 0 == brep_em[2 * i].i + || brep_em[2 * i].k >= brep_edge_count + ) + { + ON_SUBD_ERROR("Bog in SubD to joined brep code."); + continue; + } + + ON_NurbsCurve* curve2d[2] = {}; + if ( + 0 != brep_em[2 * i].j + && brep_em[2 * i].i == brep_em[2 * i + 1].i + && 0 != brep_em[2 * i + 1].j + && brep_em[2 * i + 1].k < brep_edge_count + ) + { + // This face/edge corresponds to a subd quad face and edge that + // is shared by subd N-gon with N != 4. + // This side of the brep face needs 2 brep edges so + // and will join to 2 brep faces coming from level 1 subdivision + // quads in the original subd. + const ON_2dPoint Q( + (trim_curve_cvs[0].x == trim_curve_cvs[1].x) ? trim_curve_cvs[0].x : 0.5*(trim_curve_cvs[0].x + trim_curve_cvs[1].x), + (trim_curve_cvs[0].y == trim_curve_cvs[1].y) ? trim_curve_cvs[0].y : 0.5*(trim_curve_cvs[0].y + trim_curve_cvs[1].y) + ); + trim_curve_cvs[1] = Q; + curve2d[0] = trim_curve.Duplicate(); + trim_curve_cvs[0] = Q; + trim_curve_cvs[1] = srf_uv[(i+1)%4]; + curve2d[1] = trim_curve.Duplicate(); + } + else + { + curve2d[0] = trim_curve.Duplicate(); + curve2d[1] = nullptr; + } + for ( int k = 0; k < 2; k++) + { + ON_NurbsCurve* c2 = curve2d[k]; + if (nullptr == c2) + continue; + em = brep_em[2 * i + k]; + const bool bRev3d = trimRev3d[2 * i + k]; + ON_BrepEdge& brep_edge = brep.m_E[em.k]; + ON_Interval trim_domain = brep_edge.Domain(); + if (bRev3d) + trim_domain.Reverse(); + c2->SetDomain(trim_domain[0],trim_domain[1]); + int c2i = brep.m_C2.Count(); + brep.m_C2.Append(c2); + ON_BrepTrim& brep_trim = brep.NewTrim(brep_edge, bRev3d, brep_loop, c2i); + brep_trim.m_tolerance[0] = 0.0; + brep_trim.m_tolerance[1] = 0.0; + } + } + + //Internal_DebugLocations(brep_face); + } + } + + trim_curve.m_cv = nullptr; + trim_curve.m_knot = nullptr; + + + return true; +} diff --git a/opennurbs_system.h b/opennurbs_system.h index e67045c1..c6e059b2 100644 --- a/opennurbs_system.h +++ b/opennurbs_system.h @@ -8,7 +8,7 @@ // 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 . // //////////////////////////////////////////////////////////////// @@ -64,7 +64,7 @@ // - When compiling opennurbs as a static library, ON_COMPILING_OPENNURBS // should be defined and neither OPENNURBS_EXPORTS nor OPENNURBS_IMPORTS // should be defined. -// - When using opennurbs as a static library, neither +// - When using opennurbs as a static library, neither // ON_COMPILING_OPENNURBS nor OPENNURBS_EXPORTS nor OPENNURBS_IMPORTS // should be defined. */ @@ -102,7 +102,7 @@ #if defined(ON_COMPILING_OPENNURBS) #if !defined(OPENNURBS_WALL) /* -// When OPENNURBS_WALL is defined, warnings and deprications that +// When OPENNURBS_WALL is defined, warnings and deprications that // encourage the highest quality of code are used. */ #define OPENNURBS_WALL @@ -126,13 +126,32 @@ // places before opennurbs.h or opennurbs_system.h is included. // Therefore, this define cannot be in opennurbs_system_runtime.h // -// When ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE is defined, +// When ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE is defined, // is included by opennurbs_system.h and // your project must link with the Apple Cocoa Framework. #define ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE #endif +#if defined(ON_RUNTIME_APPLE) && defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) + +// TODO: +// Requiring ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE is too strong, +// Determine exactly when ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE should +// be defined so opennurbs font / glyph tools will work on iOS. + +// The header file opennurbs_system_runtime.h is included in several +// places before opennurbs.h or opennurbs_system.h is included. +// Therefore, this define cannot be in opennurbs_system_runtime.h +// +// When ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE is defined, +// Apple Core Text and Core Graphics SDK can be used. +#define ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE + +#endif + + + #if defined(ON_64BIT_RUNTIME) /* 64 bit (8 byte) pointers */ #define ON_SIZEOF_POINTER 8 @@ -279,7 +298,7 @@ typedef ON__UINT32 wchar_t; //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // -// Validate ON_SIZEOF_WCHAR_T and set ON_WCHAR_T_ENCODING +// Validate ON_SIZEOF_WCHAR_T and set ON_WCHAR_T_ENCODING // */ @@ -359,7 +378,7 @@ typedef ON__UINT32 wchar_t; /* ///////////////////////////////////////////////////////////////////////// // -// Begin Windows system includes - +// Begin Windows system includes - */ @@ -607,6 +626,46 @@ typedef ON__UINT32 wchar_t; #endif +#if !defined(ON_RUNTIME_WIN) && !defined(ON_RUNTIME_APPLE) + +// As of September, 2018 Freetype is not reliable on Windows, MacOS, and iOS. +// Its mapping from UNICODE code points to glyph indices is buggy. +// It does not support OpenType variable fonts like Windows 10 Bahnschrift. +// It does not support font simulation (Windows does a great job here.) +// Its support for multiple locale names is limited. +// Its support for PANOSE1 is limited. +// It does not support font substitution. +// Windows uses the Direct Write SDK for font and glyph calcuations. +// MacOS and iOS use the Apple Core Text SDK for font and glyph calcuations. + +#if defined(ON_RUNTIME_ANDROID) +// May work reasonably for Andoid versions < 8-ish as of Sep 2018. +// Test carefully if working right is important. + +// We are currently not using Freetype in OpenNURBS at all. +// Leaving this in place for testing in the future if we find that we need the +// library again for glyph metrics. +// #define OPENNURBS_FREETYPE_SUPPORT +#else + +// not Windows, Apple, or Android + +// To disable freetype support, comment out the following define. +// To enable freetype support, define OPENNURBS_FREETYPE_SUPPORT +// NOTE WELL: freetype is not delivered in a 32-bit version. + +// Whenever possible use native OS tools for font and glyph support. +// Things like names, outlines, metrics, UNICODE mapping will generally +// work better align with user's experiences on that platform. +// Freetype is basically a platform neutral font file file reading toolkit +// and has all the limitations that arise from that approach to complex +// information modern OSs manage in complicated ways. + +//#define OPENNURBS_FREETYPE_SUPPORT + +#endif +#endif + /* ///////////////////////////////////////////////////////////////////////////////// // diff --git a/opennurbs_system_runtime.h b/opennurbs_system_runtime.h index 4f9018ed..d41ba995 100644 --- a/opennurbs_system_runtime.h +++ b/opennurbs_system_runtime.h @@ -47,8 +47,10 @@ #define ON_RUNTIME_WIN #endif -#elif defined(__ANDROID__) - +#elif defined(__ANDROID__) || defined(__EMSCRIPTEN__) +// __EMSCRIPTEN__ is for a web assembly compile which currently compiles with the +// same settings as an android build. We will need to add an ON_RUNTIME_WASM once +// the __EMSCRIPTEN__ compile stabilizes #if !defined(ON_RUNTIME_ANDROID) #define ON_RUNTIME_ANDROID #endif diff --git a/opennurbs_text.cpp b/opennurbs_text.cpp index 769aa5bc..de31f8d9 100644 --- a/opennurbs_text.cpp +++ b/opennurbs_text.cpp @@ -274,6 +274,7 @@ bool ON_TextContent::Internal_ParseRtf( // a different font. // The intent is to leave any font changes in place and replace // only the font used for text not explicitly set to some other font + ON_wString default_fontname = ON_Font::RichTextFontName(&dimstyle->Font(),true); ON_wString rtf_w_string(rtf_string); int ix = rtf_w_string.Find(L"{\\rtf1"); if (ix != -1) @@ -297,12 +298,12 @@ bool ON_TextContent::Internal_ParseRtf( int idx1 = rtf_w_string.Find(L";", idxface); if (idx1 > 0) { - ON_wString deffacename = rtf_w_string.SubString(idxface + 1, idx1 - idxface - 1); - if (0 != deffacename.CompareOrdinal(dimstyle_facename, true)) + default_fontname = rtf_w_string.SubString(idxface + 1, idx1 - idxface - 1); + if (0 != default_fontname.CompareOrdinal(dimstyle_facename, true)) { ON_wString t1 = rtf_w_string.Left(idxface + 1); ON_wString t2 = rtf_w_string.Right(rtf_w_string.Length() - idx1); - rtf_w_string = t1 + dimstyle_facename.Array(); + rtf_w_string = t1 + default_fontname.Array(); rtf_w_string = rtf_w_string + t2; } } @@ -342,7 +343,7 @@ bool ON_TextContent::Internal_ParseRtf( rc = ON_TextContent::MeasureTextContent(this, true, false); if (rc && bComposeAndUpdateRtf) { - rc = RtfComposer::Compose(this, dimstyle, str); + rc = RtfComposer::Compose(this, dimstyle, default_fontname, str); if (rc) { m_text = str; @@ -589,6 +590,11 @@ bool ON_TextContent::Create( h_align = dimstyle->TextHorizontalAlignment(); v_align = dimstyle->TextVerticalAlignment(); break; + case ON::AnnotationType::Diameter: + case ON::AnnotationType::Radius: + h_align = dimstyle->LeaderTextHorizontalAlignment(); + v_align = dimstyle->LeaderTextVerticalAlignment(); + break; } // Parse string, create runs, find font, set plane & height @@ -1586,6 +1592,8 @@ ON::AnnotationType ON_TextContent::Internal_AlignmentAnnotationType( { case ON::AnnotationType::Text: case ON::AnnotationType::Leader: + case ON::AnnotationType::Diameter: + case ON::AnnotationType::Radius: return annotation_type; } return ON::AnnotationType::Unset; @@ -1620,7 +1628,8 @@ const wchar_t* ON_TextContent::RtfText() const bool ON_TextContent::ComposeText() { ON_wString rtf; - if (RtfComposer::Compose(this, nullptr, rtf)) + ON_wString nothing; + if (RtfComposer::Compose(this, nullptr, nothing, rtf)) { m_text = rtf; return true; @@ -1670,14 +1679,56 @@ bool ON_TextContent::MeasureTextRun(ON_TextRun* run) double ON_TextContent::GetLinefeedHeight(ON_TextRun& run) { - double lfht = 1.6; + double lfht = ON_FontMetrics::DefaultLineFeedRatio; // (1.6) if (nullptr != run.Font()) { // March 2017 Dale Lear asks: // Why not use run.Font()->FontMetrics().LineSpace() instead // of this? - lfht = ON_FontMetrics::DefaultLineFeedRatio * run.TextHeight(); + const double text_height = run.TextHeight(); + const double legacy_lfht = ON_FontMetrics::DefaultLineFeedRatio * text_height; + + // September 2018 Dale Lear + // Fix for https://mcneel.myjetbrains.com/youtrack/issue/RH-48824 + // + // In an attempt to cause minimal distruption to the line spacing + // in existing files using common fonts + // (Arial, Klavika, Helvetica, City/Country Blueprint, ...) + // AND fix the bug for fonts like Microsoft Himalaya, I am switching + // to using line spacing value from the font metrics, BUT, I use + // this value only when it is substantually larger than the + // "1.6*HeightOfI" value we've been using since Rhino 4. + // + const ON_FontMetrics& fm = run.m_managed_font->FontMetrics(); + const double ascent_of_I = fm.AscentOfCapital(); // legacy "height of I" + const double font_line_space = fm.LineSpace(); // font designer's line spacing + const double font_metric_lfht + = (ascent_of_I > 0.0) + ? (text_height / ascent_of_I)*font_line_space + : 0.0; + + // The 1.25 threshold value was picked so that the fonts + // Arial, Arial Black, City Blueprint, Country Blueprint, Klavika*, + // Helvetica, Technic, Bahnschrift, Segoe UI, Courier, Franklin Gothic, + // Malgun Gothic, Futura, Lucida, Tahoma will continue to use line spacing + // that the have in Rhino 4, 5, 6 (up to Setp 2018). These fonts are common + // and lots of existing files have drawings with text blocks that will + // have incorrect spacing if we use the font designer's spacing. + // The font Microsoft Himalaya has font_metric_lfht/legacy_lfht = 1.4 + // so it will use the new spacing and be readable. More generally, + // if font_metric_lfht/legacy_lfht > 1.25, and the font designer set + // fm.LineSpace() to a reasonable value, then it is likely that + // multiline text will have overlapping glyphs. + lfht + = (font_metric_lfht > 1.25*legacy_lfht) + ? font_metric_lfht // this fixes https://mcneel.myjetbrains.com/youtrack/issue/RH-48824 + : legacy_lfht; + + if (!(lfht == legacy_lfht)) + ON_TextLog::Null.Print(L"Break"); } + + return lfht; } @@ -2067,7 +2118,11 @@ bool ON_TextContent::FormatDistance( ? dimstyle->AlternateDimensionLengthDisplayUnit(0) : dimstyle->DimensionLengthDisplayUnit(0); - double length_factor = dimstyle->LengthFactor(); + double length_factor = + alt + ? dimstyle->AlternateLengthFactor() + : dimstyle->LengthFactor(); + double unit_length_factor = ON::UnitScale(units_in, dim_us); distance = unit_length_factor * length_factor * distance_in; diff --git a/opennurbs_textcontext.cpp b/opennurbs_textcontext.cpp index 473e0a5c..b3fde7ca 100644 --- a/opennurbs_textcontext.cpp +++ b/opennurbs_textcontext.cpp @@ -123,6 +123,43 @@ const ON_wString ON_TextContext::FormatRtfString( dimstyle = &ON_DimStyle::Default; const ON_wString rtf_font_name = ON_Font::RichTextFontName(&dimstyle->Font(),true); + ON_wString rtf_wstring(rtf_string); + int rtf = rtf_wstring.Find("rtf1"); + if (-1 == rtf) // Input is plain text string + { + ON_wString font_table_str; + ON_wString rtf_text_str; + ON_wString fmts; + // Keep dimstyle font as f0 in the font table even if we don't need itg here + if (set_facename && !rtf_font_name.EqualOrdinal(override_facename, true)) + { + font_table_str.Format(L"{\\fonttbl{\\f0 %ls;}{\\f1 %ls;}}", rtf_font_name.Array(), override_facename); + fmts = L"\\f1"; + } + else // Use style facename + { + font_table_str.Format(L"{\\fonttbl{\\f0 %ls;}}", rtf_font_name.Array()); + fmts = L"\\f0"; + } + if (set_bold) + fmts += L"\\b"; + if (set_italic) + fmts += L"\\i"; + if (set_underline) + fmts += "L\\ul"; + + rtf_wstring.Replace(L"\\", L"\\\\"); + rtf_text_str.Format(L"{%ls %ls}", fmts.Array(), rtf_wstring.Array()); + ON_wString par; + par.Format(L"}{\\par}{%ls ", fmts.Array()); + rtf_text_str.Replace(L"\n", par.Array()); + + rtf_wstring.Format(L"{\\rtf1\\deff0%ls%ls}", font_table_str.Array(), rtf_text_str.Array()); + return rtf_wstring; + } + + // else Input is RTF string + ON_RtfStringBuilder builder(dimstyle, 1.0, ON_UNSET_COLOR); builder.SetSkipColorTbl(true); @@ -137,39 +174,16 @@ const ON_wString ON_TextContext::FormatRtfString( builder.SetOverrideFacename(override_facename); builder.SetDefaultFacename(rtf_font_name); - ON_wString rtf_wstring(rtf_string); - int rtf = rtf_wstring.Find("rtf1"); - if (-1 == rtf) + if (builder.SettingFacename()) { - ON_wString font_table_str; - ON_wString rtf_text_str; - if (builder.SettingFacename()) + int ftbl = rtf_wstring.Find(L"fonttbl"); + if (-1 == ftbl) { - font_table_str.Format(L"{\\fonttbl{\\f0 %ls;}{\\f1 %ls;}}", rtf_font_name.Array(), override_facename); - rtf_text_str.Format(L"{\\f1 %ls}", rtf_string); - rtf_text_str.Replace(L"\n", L"}{\\par}{\\f1 "); - } - else - { - font_table_str.Format(L"{\\fonttbl{\\f0 %ls;}}", rtf_font_name.Array()); - rtf_text_str.Format(L"{\\f0 %ls}", rtf_string); - rtf_text_str.Replace(L"\n", L"}{\\par}{\\f0 "); - } - rtf_wstring.Format(L"{\\rtf1\\deff0%ls%ls}", font_table_str.Array(), rtf_text_str.Array()); - } - else - { - if (builder.SettingFacename()) - { - int ftbl = rtf_wstring.Find(L"fonttbl"); - if (-1 == ftbl) - { - ON_wString temp; - len = rtf_wstring.Length(); - ON_wString str = rtf_wstring.Right(((int)len) - 7); - temp.Format(L"{\\rtf1{\\fonttbl}%ls", str.Array()); - rtf_wstring = temp; - } + ON_wString temp; + len = rtf_wstring.Length(); + ON_wString str = rtf_wstring.Right(((int)len) - 7); + temp.Format(L"{\\rtf1{\\fonttbl}%ls", str.Array()); + rtf_wstring = temp; } } len = rtf_wstring.Length(); diff --git a/opennurbs_textglyph.cpp b/opennurbs_textglyph.cpp index f934c410..2ea52835 100644 --- a/opennurbs_textglyph.cpp +++ b/opennurbs_textglyph.cpp @@ -32,7 +32,7 @@ void ON_FontGlyph::Internal_CopyFrom(const ON_FontGlyph& src) m_managed_font = src.m_managed_font; m_substitute = src.m_substitute; // Do not copy m_is_managed. - m_font_glyph_id = src.m_font_glyph_id; + m_font_glyph_index = src.m_font_glyph_index; } ON_FontGlyph::ON_FontGlyph(const ON_FontGlyph& src) @@ -51,9 +51,9 @@ ON_FontGlyph& ON_FontGlyph::operator=(const ON_FontGlyph& src) return *this; } -void ON_FontGlyph::Internal_SetFontGlyphId(ON__UINT_PTR font_glyph_id) +void ON_FontGlyph::Internal_SetFontGlyphIndex(unsigned int font_glyph_index) { - m_font_glyph_id = font_glyph_id; + m_font_glyph_index = font_glyph_index; } ON_FontGlyph::ON_FontGlyph( @@ -363,32 +363,34 @@ const ON__UINT32 ON_FontGlyph::CodePoint() const return m_code_point; } -const ON__UINT_PTR ON_FontGlyph::FontGlyphId() const +unsigned int ON_FontGlyph::FontGlyphIndex() const { - if (0 != m_font_glyph_id) - return m_font_glyph_id; + if (0 != m_font_glyph_index) + return m_font_glyph_index; const ON_FontGlyph* managed_glyph = ManagedGlyph(); return nullptr == managed_glyph ? 0 - : managed_glyph->m_font_glyph_id; + : managed_glyph->m_font_glyph_index; +} + + +bool ON_FontGlyph::FontGlyphIndexIsSet() const +{ + return (0 != m_font_glyph_index); +} + +const ON__UINT_PTR ON_FontGlyph::FontGlyphId() const +{ + return (ON__UINT_PTR)FontGlyphIndex(); } bool ON_FontGlyph::FontGlyphIdIsSet() const { - return (0 != m_font_glyph_id); + return FontGlyphIndexIsSet(); } -const ON__UINT_PTR ON_FontGlyph::FreeTypeFace() const -{ - return - (nullptr == m_managed_font) - ? 0 - : ON_Font::FreeTypeFace(m_managed_font); -} - - bool ON_FontGlyph::IsEndOfLineCodePoint() const { return ON_FontGlyph::IsEndOfLineCodePoint(m_code_point); @@ -476,7 +478,7 @@ const ON_FontGlyph* ON_FontGlyph::RenderGlyph( continue; if (nullptr != managed_glyph->m_substitute) return managed_glyph->m_substitute; - if (0 == glyph->m_font_glyph_id && bUseReplacementCharacter) + if (0 == glyph->m_font_glyph_index && bUseReplacementCharacter) continue; return glyph; } @@ -550,7 +552,7 @@ void ON_FontGlyph::Dump( if ( ON_IsValidUnicodeCodePoint(g->CodePoint()) ) { const unsigned int code_point = g->CodePoint(); - const unsigned int glyph_id = (unsigned int)g->FontGlyphId(); + const unsigned int glyph_index = g->FontGlyphIndex(); wchar_t w[8] = { 0 }; ON_EncodeWideChar(code_point, 7, w); const ON_Font* font = g->Font(); @@ -578,9 +580,9 @@ void ON_FontGlyph::Dump( } } - if (glyph_id > 0) + if (glyph_index > 0) { - s += ON_wString::FormatToString(L" glyph id = %u", glyph_id); + s += ON_wString::FormatToString(L" glyph index = %u", glyph_index); bPrintMaps = bIncludeCharMaps; } else if (bIncludeFont) @@ -626,12 +628,18 @@ void ON_FontGlyph::Dump( text_log.PrintString(s); text_log.PrintNewLine(); + +#if defined(OPENNURBS_FREETYPE_SUPPORT) + // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. + // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). if ( bPrintMaps && nullptr != g ) { text_log.PushIndent(); - g->TestFaceCharMaps(&text_log); + g->TestFreeTypeFaceCharMaps(&text_log); text_log.PopIndent(); } +#endif + } @@ -1156,4 +1164,334 @@ const ON_2fPoint ON_OutlineFigurePoint::Point() const const ON_2dPoint ON_OutlineFigurePoint::Point2d() const { return ON_2dPoint(m_point); -} \ No newline at end of file +} + + +bool ON_Annotation::GetTextGlyphContours( + const ON_Viewport* vp, + const ON_DimStyle* dimstyle, + bool bApplyDimStyleDimScale, + bool bSingleStrokeFont, + ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours +) const +{ + const ON_TextContent* text_content = Text(); + if (nullptr == text_content) + return false; + + double text_scale = 0.0; + if (bApplyDimStyleDimScale && nullptr != dimstyle) + { + text_scale = dimstyle->DimScale(); + } + if (false == (text_scale > 0.0 && ON_IsValid(text_scale))) + text_scale = 1.0; + + + ON_Xform text_xform = ON_Xform::IdentityTransformation; + if (false == this->GetTextXform(vp, dimstyle, text_scale, text_xform)) + text_xform = ON_Xform::IdentityTransformation; + + const ON_Font* text_font = (nullptr != dimstyle) ? &dimstyle->Font() : nullptr; + + return text_content->GetGlyphContours(text_font, bSingleStrokeFont, text_xform, text_contours); +} + +bool ON_TextContent::GetGlyphContours( + const ON_Font* text_font, + bool bSingleStrokeFont, + double text_height, + ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours +) const +{ + ON_Xform text_xform = ON_Xform::IdentityTransformation; + + double scale = 1.0; + if (text_height > 0.0 && ON_IsValid(text_height) ) + { + if (nullptr == text_font) + text_font = &ON_Font::Default; + scale = text_font->FontMetrics().GlyphScale(text_height); + if (scale > 0.0) + text_xform = ON_Xform::DiagonalTransformation(scale); + } + + return this->GetGlyphContours( + text_font, + bSingleStrokeFont, + text_xform, + text_contours + ); +} + + +static const ON_FontGlyph* Internal_GetGlyphContours_SmallCapsGlyph( + const ON_FontGlyph* glyph +) +{ + if (nullptr == glyph || false == glyph->CodePointIsSet() ) + return nullptr; + const ON_FontGlyph* small_caps_glyph = nullptr; + const ON__UINT32 code_point = glyph->CodePoint(); + const ON__UINT32 upper_ordinal_code_point = ON_UnicodeMapCodePointOrdinal(ON_StringMapOrdinalType::UpperOrdinal, code_point); + if ( + upper_ordinal_code_point != code_point + && upper_ordinal_code_point >= 'A' + && ON_IsValidUnicodeCodePoint(upper_ordinal_code_point) + ) + { + small_caps_glyph = glyph->Font()->CodePointGlyph(upper_ordinal_code_point); + if (nullptr != small_caps_glyph) + { + if (glyph->Font() != small_caps_glyph->Font() || small_caps_glyph != small_caps_glyph->RenderGlyph(false)) + { + // do not permit font or glyph substitution when "small caps" are used. + small_caps_glyph = nullptr; + } + } + } + return small_caps_glyph; +} + +bool ON_FontGlyph::GetStringContours( + const wchar_t* text_string, + const ON_Font* font, + bool bSingleStrokeFont, + double text_height, + double small_caps_scale, + ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& string_contours +) +{ + // Dale Lear: https://mcneel.myjetbrains.com/youtrack/issue/RH-38183 + // Font substitution has to be used to get outlines for all code points. + // I rewrote this entire function to support use of multiple fonts in a single string + // to fix RH-38183. + const bool bUseReplacementCharacter = true; + + if (nullptr == text_string || 0 == text_string[0]) + return false; + + const ON_Font* primary_font = (nullptr != font) ? font->ManagedFont() : ON_Font::Default.ManagedFont(); + if (nullptr == primary_font) + return false; + + const ON_FontMetrics primary_fm = primary_font->FontMetrics(); + + double scale = (text_height > ON_ZERO_TOLERANCE && text_height < 1.e38) + ? primary_fm.GlyphScale(text_height) + : 0.0; + if (false == (scale > ON_ZERO_TOLERANCE && ON_IsValid(scale)) ) + { + text_height = 0.0; + scale = 1.0; + } + const double height_of_LF = scale*primary_fm.LineSpace(); + + if (false == (text_height > ON_ZERO_TOLERANCE && text_height < 1.e38)) + text_height = 0.0; + + const double small_caps_text_height + = (small_caps_scale > ON_ZERO_TOLERANCE && small_caps_scale < 1.0) + ? small_caps_scale*text_height + : text_height; + + ON_SimpleArray< const ON_FontGlyph* > glyph_list; + ON_TextBox text_box; + if (ON_FontGlyph::GetGlyphList( + text_string, + primary_font, + ON_UnicodeCodePoint::ON_LineSeparator, + glyph_list, + text_box) <= 0) + { + return false; + } + + double line_advance = 0.0; + ON_3dPoint glyph_base_point = ON_3dPoint::Origin; + + unsigned int glyph_count = glyph_list.UnsignedCount(); + for ( unsigned int gdex = 0; gdex < glyph_count; gdex++ ) + { + const ON_FontGlyph* glyph = glyph_list[gdex]; + if (nullptr == glyph) + continue; + if (glyph->IsEndOfLineCodePoint()) + { + line_advance += height_of_LF; + glyph_base_point.x = 0; + glyph_base_point.y = line_advance; + continue; + } + + glyph = glyph->RenderGlyph(bUseReplacementCharacter); + if (nullptr == glyph) + continue; + + double glyph_text_height = text_height; + + const ON_FontGlyph* small_caps_glyph = + (small_caps_text_height > 0.0 && small_caps_text_height < text_height) + ? Internal_GetGlyphContours_SmallCapsGlyph(glyph) + : glyph; + if (nullptr != small_caps_glyph) + { + glyph_text_height = small_caps_text_height; + glyph = small_caps_glyph; + } + + ON_BoundingBox glyph_contours_bbox = ON_BoundingBox::UnsetBoundingBox; + ON_3dVector glyph_contours_advance = ON_3dVector::ZeroVector; + ON_ClassArray< ON_SimpleArray< ON_Curve* > >& glyph_contours = string_contours.AppendNew(); + glyph->GetGlyphContours(bSingleStrokeFont, glyph_text_height, glyph_contours, &glyph_contours_bbox, &glyph_contours_advance); + + const ON_3dVector translate = glyph_base_point; + glyph_base_point.x += glyph_contours_advance.x; + + const int contour_count = glyph_contours.Count(); + + for (int li = 0; li < contour_count; li++) // contours per glyph + { + const int curve_count = glyph_contours[li].Count(); + for (int ci = 0; ci < curve_count; ci++) + { + if (nullptr != glyph_contours[li][ci]) + glyph_contours[li][ci]->Translate(translate); + } + } + } + + return true; +} + +bool ON_TextRun::GetGlyphContours( + const ON_Font* text_font, + bool bSingleStrokeFont, + const ON_Xform& text_xform, + ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& run_contours +) const +{ + const ON_TextRun& run = *this; + + const ON_Font* run_font = run.Font(); + if (nullptr == run_font) + { + run_font = text_font; + if (nullptr == run_font) + run_font = &ON_Font::Default; + } + + ON_Xform run_xf(text_xform); + + if (0.0 != run.m_offset.x || 0.0 != run.m_offset.y) + { + const ON_Xform run_offset(ON_Xform::TranslationTransformation(run.m_offset.x, run.m_offset.y, 0.0)); + run_xf = text_xform * run_offset; + } + + double run_height = run.TextHeight(); // Specified height of text in Model units + double I_height = run_font->FontMetrics().AscentOfCapital(); + double font_scale = run_height / I_height; // converts Font units to Model units, including text height + ON_Xform scale_xf(ON_Xform::DiagonalTransformation(font_scale)); + run_xf = run_xf * scale_xf; + + if (run.IsStacked() == ON_TextRun::Stacked::kStacked && nullptr != run.m_stacked_text) + { + const ON_TextRun* stacked_runs[2] = + { + run.m_stacked_text->m_top_run, + run.m_stacked_text->m_bottom_run, + }; + bool rc = false; + for (int i = 0; i < 2; i++) + { + if (nullptr == stacked_runs[i]) + continue; + if (stacked_runs[i]->GetGlyphContours( + run_font, + bSingleStrokeFont, + text_xform, + run_contours + )) + rc = true; + } + + //if (L'/' == run.m_stacked_text->m_separator) + //{ + // double h = 0.5 * I_height; + // double hs = (double)font->GetUnderscoreSize(); + // double l = run.m_advance.x / font_scale; + // DrawFracLine(*this, run_xf, 0.0, h, hs, l, color); + //} + return rc; + } + + + // run->UnicodeString() returns the raw string which may have unevaluated fields. + // run->DisplayString() returns the evaluated results of fields. + const int run_contours_count0 = run_contours.Count(); + bool rc = ON_FontGlyph::GetStringContours( + run.DisplayString(), + run_font, + bSingleStrokeFont, + 0.0, // text_height = 0.0 means get glyphs in openurbs normalized font size + 0.0, // small_caps_scale, + run_contours + ); + + const int run_contours_count1 = run_contours.Count(); + for (int gi = run_contours_count0; gi < run_contours_count1; gi++) + { + ON_ClassArray< ON_SimpleArray< ON_Curve* > >& countours = run_contours[gi]; + const int countour_count = countours.Count(); + for (int li = 0; li < countour_count; li++) + { + ON_SimpleArray< ON_Curve* >& curves = countours[li]; + const int curve_count = curves.Count(); + for (int ci = 0; ci < curve_count; ci++) + { + ON_Curve* curve = curves[ci]; + if (curve) + curve->Transform(run_xf); + } + } + } + + return rc; +} + +bool ON_TextContent::GetGlyphContours( + const ON_Font* text_font, + bool bSingleStrokeFont, + const ON_Xform& text_xform, + ON_ClassArray< ON_ClassArray< ON_SimpleArray< ON_Curve* > > >& text_contours +) const +{ + if (nullptr == text_font) + text_font = &ON_Font::Default; + + const ON_Xform xf = text_xform; + + const ON_TextRunArray* runs = TextRuns(false); + if (nullptr != runs) + { + const int runcount = runs->Count(); + for (int ri = 0; ri < runcount; ri++) + { + const ON_TextRun* run = (*runs)[ri]; + if (nullptr == run) + continue; + if (ON_TextRun::RunType::kText != run->Type() && ON_TextRun::RunType::kField != run->Type()) + continue; + + const ON_Font* run_font = run->Font(); + if (nullptr == run_font) + run_font = text_font; + + run->GetGlyphContours(run_font, bSingleStrokeFont, xf, text_contours); + } + } + + return false; +} + diff --git a/opennurbs_textiterator.cpp b/opennurbs_textiterator.cpp index 398dd2a3..695dd891 100644 --- a/opennurbs_textiterator.cpp +++ b/opennurbs_textiterator.cpp @@ -313,7 +313,7 @@ ON__UINT32 ON_RtfParser::Internal_ParseMBCSString( const ON__UINT32 windows_code bParsed = false; break; } - m_builder.m_current_codepoints.Append(unicode_code_point); + m_builder.AppendCodePoint(unicode_code_point); } } delete[] sWideChar; @@ -322,7 +322,7 @@ ON__UINT32 ON_RtfParser::Internal_ParseMBCSString( const ON__UINT32 windows_code } if (false == bParsed) - m_builder.m_current_codepoints.Append(ON_UnicodeCodePoint::ON_ReplacementCharacter); + m_builder.AppendCodePoint(ON_UnicodeCodePoint::ON_ReplacementCharacter); return count; } @@ -1072,28 +1072,16 @@ void ON_TextRunBuilder::FinishCurrentRun() #endif // 0 AppendCurrentRun(); } - //else if (ReadingFontDefinition() && m_current_run.Type() == ON_TextRun::RunType::kFontdef) - //{ - // // String is a font "facename" (In reality the "facename" it can be a PostScript name, a LOGFONT.lfFaceName, - // // or a DWrite family name - depending on who is making the RTF). - // // Make a font with that facename and a font definition run - // size_t cpcount = ON_TextRun::CodepointCount(RunCodePoints(m_current_run)); - // if (0 != cpcount) - // { - // ON_wString str; - // ON_TextContext::ConvertCodepointsToString((int)cpcount, RunCodePoints(m_current_run), str); - // if (!str.IsEmpty()) - // { - // str.Remove(L';'); // facename delimiter from rtf - // ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); - // fn_key.m_rtf_font_index = m_font_index; - // fn_key.m_rtf_font_name = str; - // fn_key.m_codepage = m_current_props.CodePage(); - // fn_key.m_charset = m_current_props.CharSet(); - // } - // } - //} } + +static bool IsValidFontName(const ON_wString& name) +{ + // Test to prevent making fonts named "(varies)" + if (L'(' == name[0] && L')' == name[name.Length()-1]) + return false; + return true; +} + void ON_TextRunBuilder::FinishFontDef() { if (ReadingFontDefinition()) @@ -1115,6 +1103,12 @@ void ON_TextRunBuilder::FinishFontDef() if (!str.IsEmpty()) { str.Remove(L';'); // facename delimiter from rtf + + if (!IsValidFontName(str)) + { + ON_ERROR("Invalid font name found in rtf string"); + str = L"Arial"; + } ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); fn_key.m_rtf_font_index = m_font_index; fn_key.m_rtf_font_name = str; @@ -1314,89 +1308,6 @@ static const ON_Font* Internal_UpdateManagedFont( return current_managed_font; } -//const ON_Font* Internal_UpdateManagedFont( -// const ON_Font* current_managed_font, -// const ON_wString rtf_font_name, -// bool rtf_bBold, -// bool rtf_bItalic, -// bool rtf_bUnderlined, -// bool rtf_bStrikethrough -//) -//{ -// if (nullptr != current_managed_font && false == current_managed_font->IsManagedFont()) -// current_managed_font = current_managed_font->ManagedFont(); -// if (nullptr == current_managed_font) -// current_managed_font = &ON_Font::Default; -// -// bool bChangeFontFamilyName = false; -// for (;;) -// { -// if (rtf_font_name.IsEmpty()) -// break; -// if (Internal_EqualFontName(rtf_font_name, current_managed_font->FamilyName())) -// break; -// if (Internal_EqualFontName(rtf_font_name, current_managed_font->PostScriptName())) -// break; -// const ON_wString windows_logfont_name = current_managed_font->WindowsLogfontName(); -// if (Internal_EqualFontName(rtf_font_name, windows_logfont_name)) -// break; -// ON_wString rtf_family_name = ON_Font::FamilyNameFromDirtyName(rtf_font_name); -// if (Internal_EqualFontName(rtf_family_name, current_managed_font->FamilyName())) -// break; -// bChangeFontFamilyName = true; -// break; -// } -// -// const bool bChangeWeight = ((rtf_bBold ? true : false) != current_managed_font->IsBoldInQuartet()); -// const bool bChangeStyle = ((rtf_bItalic ? true : false) != current_managed_font->IsItalic()); -// const bool bChangeUnderlined = ((rtf_bUnderlined ? true : false) != current_managed_font->IsUnderlined()); -// const bool bChangeStrikethrough = ((rtf_bStrikethrough ? true : false) != current_managed_font->IsStrikethrough()); -// -// const bool bUpdateFont -// = bChangeFontFamilyName -// || bChangeWeight -// || bChangeStyle -// || bChangeUnderlined -// || bChangeStrikethrough; -// -// if (false == bUpdateFont) -// return current_managed_font; -// -// const ON_Font::Weight font_weight -// = bChangeWeight -// ? (rtf_bBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal) -// : (current_managed_font->FontWeight()); // IsBold() ? ON_Font::Weight::Bold : ON_Font::Weight::Normal); -// const ON_Font::Style font_style -// = bChangeStyle -// ? (rtf_bItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright) -// : current_managed_font->FontStyle(); -// const bool bUnderlined -// = bChangeUnderlined -// ? rtf_bUnderlined -// : current_managed_font->IsUnderlined(); -// const bool bStrikethrough -// = bChangeStrikethrough -// ? rtf_bStrikethrough -// : current_managed_font->IsStrikethrough(); -// -// ON_Font updated_font(*current_managed_font); -// if (bChangeFontFamilyName) -// { -// ON_wString rtf_family_name = ON_Font::FamilyNameFromDirtyName(rtf_font_name); -// updated_font.SetFamilyName(rtf_family_name); -// } -// -// updated_font.SetFontWeight(font_weight); -// updated_font.SetFontStyle(font_style); -// updated_font.SetUnderlined(bUnderlined); -// updated_font.SetStrikethrough(bStrikethrough); -// -// const ON_Font* new_managed_font = updated_font.ManagedFont(); -// if (nullptr != new_managed_font) -// return new_managed_font; -// -// return current_managed_font; -//} // Process a rtf \fn tag to set the current font to the nth font in the rtf font table void ON_TextRunBuilder::FontTag(const wchar_t* value) @@ -1940,9 +1851,10 @@ void ON_RtfStringBuilder::BeginFontTable() return; ON_wString temp; - temp.Format(L"\\fonttbl{\\f0 %s;}", m_default_facename.Array()); + //temp.Format(L"\\fonttbl{\\f0 %s;}", m_default_facename.Array()); + temp.Format(L"\\fonttbl"); m_current_run.AddText(temp.Array()); - if (SettingFacename()) + if (SettingFacename() && !m_default_facename.EqualOrdinal(m_override_facename, true)) { temp.Format(L"{\\f1 %s;}", m_override_facename.Array()); m_current_run.AddText(temp.Array()); @@ -1953,12 +1865,6 @@ void ON_RtfStringBuilder::BeginHeader() { m_current_run.SetType(ON_TextRun::RunType::kHeader); m_current_run.AddControl(L"\\rtf1"); - if (MakeBold()) - m_current_run.AddControl(L"\\b"); - if (MakeItalic()) - m_current_run.AddControl(L"\\i"); - if (MakeUnderline()) - m_current_run.AddControl(L"\\ul"); } // Sets m_default_font_index when \deffn is read. @@ -1990,8 +1896,6 @@ const ON_wString ON_RtfStringBuilder::OutputString() // or to store the definition for the nth font in the font table void ON_RtfStringBuilder::FontTag(const wchar_t* value) { - if(ReadingFontTable()) - m_current_run.SetType(ON_TextRun::RunType::kFontdef); if (SkippingFacename()) return; @@ -2002,6 +1906,7 @@ void ON_RtfStringBuilder::FontTag(const wchar_t* value) { if (ReadingFontTable()) { + m_current_run.SetType(ON_TextRun::RunType::kFontdef); if (!SettingFacename()) { // Defining a font in the font table @@ -2019,17 +1924,21 @@ void ON_RtfStringBuilder::FontTag(const wchar_t* value) { // Not defining the font table. Rather, setting a font current // Set current font to font corresponding to font_index + if (SkippingFacename() || SettingFacename()) + m_current_run.AddControl(L"\\f1"); + else if (m_current_run.FontIndex() != nval) { - if (SkippingFacename() || SettingFacename()) - m_current_run.AddControl(L"\\f1"); - else if (m_current_run.FontIndex() != nval) - { - ON_wString temp; - temp.Format(L"\\f%d", nval); - m_current_run.AddControl(temp.Array()); - m_current_run.SetFontIndex(nval); - } + ON_wString temp; + temp.Format(L"\\f%d", nval); + m_current_run.AddControl(temp.Array()); + m_current_run.SetFontIndex(nval); } + if (MakeBold()) + m_current_run.AddControl(L"\\b"); + if (MakeItalic()) + m_current_run.AddControl(L"\\i"); + if (MakeUnderline()) + m_current_run.AddControl(L"\\ul"); } } @@ -2239,6 +2148,8 @@ bool ON_RtfStringBuilder::AppendCodePoint(ON__UINT32 codept) ON_wString str; ON_TextContext::ConvertCodepointsToString(1, &codept, str); + if (codept == ON_UnicodeCodePoint::ON_Backslash || codept == L'{' || codept == L'}') + m_current_run.AddText(L"\\"); m_current_run.AddText(str.Array()); m_current_codepoints.Append(codept); @@ -2678,7 +2589,8 @@ bool ON_RtfParser::ReadTag(bool optional) else // tag name terminated by any non-alphabetic char, tag parameter argument terminated by non-digit char { end_of_tag = true; - m_builder.FormatChange(); + if(0 != ON_wString::CompareOrdinal(name, tagUniCharDec, true)) + m_builder.FormatChange(); rc = ProcessTag(name, value, optional); // Terminating chars other than these are eaten here @@ -2745,7 +2657,7 @@ bool ON_RtfParser::Parse() case ON_UnicodeCodePoint::ON_Backslash: case L'{': case L'}': - m_builder.m_current_codepoints.Append(rtf_code_point); + m_builder.AppendCodePoint(rtf_code_point); break; // Paragraph tag when following '\\' @@ -2875,7 +2787,7 @@ unsigned int RtfComposer::GetFacenameKey(const ON_Font* font, ON_SimpleArray< wc return 0; // Depending on what created the RTF, the face name in the RTF can be a // PostScript name, LOGFONT.lfFaceName, IDWriteFont family name, ... - const ON_wString rtf_facename = ON_Font::RichTextFontName(font,true); + const ON_wString rtf_facename = font->QuartetName(); // ON_Font::RichTextFontName(font, true); if (rtf_facename.IsEmpty()) return 0; @@ -3007,7 +2919,11 @@ static bool GetRunText(ON_TextRun* run, ON_wString& text_out, bool& foundunicode } -bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyle, ON_wString& rtf) +bool RtfComposer::Compose( + const ON_TextContent* text, + const ON_DimStyle* dimstyle, + const ON_wString default_fontname, + ON_wString& rtf) { dimstyle = &ON_DimStyle::DimStyleOrDefault(dimstyle); @@ -3024,6 +2940,13 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl if (nullptr == runs) return false; + //if (dimstyle->IsChildDimstyle()) + //{ + // ON_UUID parent_id = dimstyle->ParentId(); + // + //} + + const ON_Font& style_font = dimstyle->Font(); bool style_bold = style_font.IsBoldInQuartet(); @@ -3036,13 +2959,17 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl bool chg_underline = false; bool chg_strikeout = false; bool chg_facename = false; + if (dimstyle->IsChildDimstyle() && dimstyle->IsFieldOverride(ON_DimStyle::field::Font)) + chg_facename = true; // First facename is from the ON_TextContent // Any after that are from runs ON_SimpleArray< wchar_t[34] > fonttable(8); // Creates a fonttable entry the first time - unsigned int deffont_key = GetFacenameKey(&style_font, fonttable); + unsigned int deffont_key = default_fontname.IsNotEmpty() ? + GetFacenameKey(default_fontname, fonttable) : + GetFacenameKey(&style_font, fonttable); int runcount = runs->Count(); int nlcount = 0; @@ -3078,7 +3005,6 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl if (nullptr != run_font) { runholders.AppendNew() = run; - unsigned int facename_key = GetFacenameKey(run_font, fonttable); if(!chg_bold) chg_bold = run_font->IsBoldInQuartet() != style_bold; if(!chg_italic) @@ -3087,6 +3013,8 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl chg_underline = run_font->IsUnderlined() != style_underline; if(!chg_strikeout) chg_strikeout = run_font->IsStrikethrough() != style_strikeout; + + unsigned int facename_key = GetFacenameKey(run_font, fonttable); if(!chg_facename) chg_facename = facename_key != deffont_key; } @@ -3095,7 +3023,11 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl { for (int i = 0; run_codepoints[i] != 0; i++) { - if (run_codepoints[i] > 127) + if (run_codepoints[i] > 127 + || run_codepoints[i] == '\\' + || run_codepoints[i] == '{' + || run_codepoints[i] == '}' + ) { make_rtf = true; break; @@ -3143,7 +3075,6 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl run_strings += L"{"; bool addspace = false; unsigned int run_font_key = GetFacenameKey(run_font, fonttable); - if (run_font_key != deffont_key) { temp.Format(L"\\f%d", run_font_key); run_strings += temp; @@ -3235,12 +3166,8 @@ bool RtfComposer::Compose(const ON_TextContent* text, const ON_DimStyle* dimstyl fonttable_string += "}"; rtf += fonttable_string; } - temp.Format(L"{\\f%d ", deffont_key); - rtf += temp; - rtf += run_strings; - - rtf += L"\\par}}"; + rtf += L"\\par}"; } else { @@ -3259,412 +3186,3 @@ void RtfComposer::SetRecomposeRTF(bool b) RtfComposer::m_bComposeRTF = b; } -#if 0 -//static -void RtfComposer::ComposeRun( - const ON_TextRun* run, - const ON_DimStyle* dimstyle, - ON_SimpleArray< wchar_t[34] >& fonttable, - bool multiline, - int& changecount, - int& changefont, - int& changecolor, - bool& bold, - bool& italic, - bool& underlined, - bool& strikeout, - ON_wString& strings_out) -{ - bool chg_bold = false; - bool chg_italic = false; - bool chg_underlined = false; - bool chg_strikeout = false; - bool chg_subscript = false; - bool chg_superscript = false; - bool chg_color = false; - bool chg_height = false; - bool chg_facename = false; - unsigned int facenamekey = 0; - - if (nullptr == dimstyle) - dimstyle = &ON_DimStyle::Default; - - void* pft = &fonttable; - if (nullptr == pft) - return; - - const ON_Font& style_font = dimstyle->Font(); - - const ON_Font* run_font = run->Font(); - if (nullptr == run_font) - run_font = &ON_Font::Default; - - const ON_wString style_facename = ON_Font::RichTextFontName(&style_font,false); - const ON_wString run_facename = ON_Font::RichTextFontName(run_font,false); - - if (run_facename.IsNotEmpty() && false == ON_wString::EqualOrdinal(style_facename, run_facename, true)) - { - chg_facename = true; - facenamekey = GetFacenameKey(run_font, fonttable); - } - if (run_font->IsBoldInQuartet() != (bold ? true : false)) - { - chg_bold = true; - } - if ((ON_Font::Style::Italic == run_font->FontStyle()) != italic) - { - chg_italic = true; - } - if (run_font->IsUnderlined() != underlined) - { - chg_underlined = true; - } - if (run_font->IsStrikethrough() != strikeout) - { - chg_strikeout = true; - } - - // Any change here means we need a real rtf string, not just plain text - if (chg_bold || chg_italic || chg_underlined || chg_strikeout || chg_subscript || chg_superscript || chg_color || chg_height || chg_facename || multiline) - { - ON_wString temp; - changecount++; - if (chg_facename) - { - temp.Format(L"{\\f%d", facenamekey); - strings_out += temp; - } - else - strings_out += L"{"; - //if (run->FlowDirection() == ON_TextRun::RunDirection::kLtR) - // strings_out += L"{\\ltrch"; - //else - // strings_out += L"{\\rtlch"; - - - if (chg_bold) - { - if (run_font->IsBoldInQuartet()) - strings_out += L"\\b"; - else - strings_out += L"\\b0"; - } - if (chg_italic) - { - if (ON_Font::Style::Italic == run_font->FontStyle()) - strings_out += L"\\i"; - else - strings_out += L"\\i0"; - } - if (chg_underlined) - { - if (run_font->IsUnderlined()) - strings_out += L"\\ul"; - else - strings_out += L"\\ul0"; - } - wchar_t last = strings_out[strings_out.Length() - 1]; - if (last != L';' && last != L'{') - strings_out += L" "; - - // Have to convert codepoints directly instead of - // calling DisplayString() because of field evaluation - // and because RTF uses a "signed UTF-16" encoding and converting the run - // into RTF has to work on Apple platforms where wchar_t strings - // are UTF-32 encoded strings. (Ask Dale Lear if you have questions). - const ON__UINT32* run_code_points = run->UnicodeString(); // null terminated - if (nullptr != run_code_points && 0 != run_code_points[0]) - { - for (int ci = 0; 0 != run_code_points[ci]; ci++) - { - const ON__UINT32 code_point = run_code_points[ci]; - - // works on Windows and Apple - ON__UINT16 utf16[2] = { 0 }; - const int utf16_count = ON_EncodeUTF16(code_point, utf16); - if (utf16_count < 0 || utf16_count > 2 || 0 == utf16[0]) - continue; - - if (code_point > 0x80 || 1 != utf16_count || code_point != (ON__UINT32)utf16[0]) - { - // When we write RTF, we do not specify what encodding is used for values in the range 0x80 - 0xFF. - // - // The ON_wString temp should to have UTF-16 encoding on Windows platforms - // and UTF-32 encoding on Apple platforms. - // - // There are 27 "tricky values" in this range 0x80 - 0xFF where Windows-1252 maps the value to a glyph and - // and UNICODE maps the value to a control that typically has no printable glyph. - // These "tricky values" are all in the range 0x80 ... 0x9F. - // An example is the Euro sign (Windows-1252 0x80 = Euro sign, UNICODE U+0080 = xxx control, - // UNOCODE U+20AC = Euro sign). - // - // The RTF we get from Windows controls, like the "Text" command dialog box, - // typically specifies it is using Windows-1252 and encodes the Euro sign as \`80. - // So, if we have one of these "euro like" values, we will explicitly write it as a UNICODE value - // to avoid the possiblity of something defaulting to using Windows-1252. - // https://mcneel.myjetbrains.com/youtrack/issue/RH-38205 - // - // See ON_DecodeWindowsCodePage1252Value() for more details. - // - // UNOCODE code points that require UTF-16 surrogate pair encodings have - // two RTF values TWO \uN?\uN? values. - // For example, UNOCODE code point U+1F5D1 has UTF-16 encodeing (0xD83D, 0xDDD1) - // and the RTF looks like ...{\ltrch \u-10179?\u-8751?}. - for (int utf16i = 0; utf16i < utf16_count; utf16i++) - { - ON_wString n; - const ON__INT16 signed_rtf_utf16_value = (ON__INT16)utf16[utf16i]; // will be negative when utf16[utf16i] >= 0x8000; - const int signed_string_format_param = (int)signed_rtf_utf16_value; - n.Format(L"\\u%d?", signed_string_format_param); - strings_out += n; - } - } - else - { - // code_point < 0x80 (ASCII value range) and casting to wchar_t will work - // on any platform; - wchar_t ascii_value = (wchar_t)code_point; - if (IsSpecial(ascii_value)) - { - strings_out += L'\\'; - } - strings_out += ascii_value; - } - } - } - strings_out += L"}"; - //if (chg_facename) - // strings_out += L"}"; - } - else - { - ON_wString temp; - size_t cplen = ON_TextRun::CodepointCount(run->UnicodeString()); - ON_TextContext::ConvertCodepointsToString((int)cplen, run->UnicodeString(), temp); - if (!temp.IsEmpty()) - { - int len = temp.Length(); - for (int si = 0; si < len; si++) - { - if (temp[si] > 0xFE) - { - // convert to rtf unicode string - ON_wString n; - n.Format(L"\\u%d?", (short)temp[si]); - strings_out += n; - } - else - { - strings_out += temp[si]; - if (temp[si] == '\\') - strings_out += '\\'; - } - } - } - } -} - -bool RtfComposer::ComposeA(const ON_TextContent* text, const ON_DimStyle* dimstyle, ON_wString& rtf) -{ - dimstyle = &ON_DimStyle::DimStyleOrDefault(dimstyle); - - if (0 == text) - return false; - - if (!RtfComposer::RecomposeRTF()) - { - rtf = text->RtfText(); - return true; - } - - ON_TextRunArray* runs = text->TextRuns(true); - if (nullptr == runs) - return false; - - const ON_Font& style_font = dimstyle->Font(); - bool bold = style_font.IsBoldInQuartet(); - bool italic = (ON_Font::Style::Italic == style_font.FontStyle()); - bool underlined = style_font.IsUnderlined(); - bool strikeout = style_font.IsStrikethrough(); - - // First color and first facename are from the ON_TextContent - // Any after that are from runs - ON_SimpleArray< unsigned int > colortable; - ON_SimpleArray< wchar_t[34] > fonttable(8); - int changecount = 0; // count all changes except font and color (because they are in tables in rtf) - int changecolor = 0; // count changes in color - int changefont = 0; // count changes in font - - unsigned int deffont_index = GetFacenameKey(&style_font, fonttable); - - ON_ClassArray< ON_String > lines; - ON_wString fonttbl_string; - ON_wString colortbl; - ON_wString strings; - ON_wString temp; - - int runcount = runs->Count(); - int nlcount = 0; - bool multiline = false; - for (int ri = 0; ri < runcount; ri++) - { - ON_TextRun* run = (*runs)[ri]; - if (0 != run) - { - if (ON_TextRun::RunType::kText == run->Type() && 0 < nlcount) - multiline = true; - else if (ON_TextRun::RunType::kNewline == run->Type() || - ON_TextRun::RunType::kParagraph == run->Type()) - nlcount++; - } - } - - for (int ri = 0; ri < runcount; ri++) - { - ON_TextRun* run = (*runs)[ri]; - if (0 != run) - { - if (ON_TextRun::RunType::kText == run->Type() || - ON_TextRun::RunType::kField == run->Type()) - { - const ON_Font* run_font = run->Font(); - if (nullptr != run_font) - { - if (run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0) - { - // See if this run is all digits and delimiter - temp.Empty(); - size_t cplen = ON_TextRun::CodepointCount(run->UnicodeString()); - ON_TextContext::ConvertCodepointsToString((int)cplen, run->UnicodeString(), temp); - -#ifndef ON_TEXT_BRACKET_FRACTION // Stacked fraction brackets - bool alldigits = true; - if (!temp.IsEmpty()) - { - int len = temp.Length(); - for (int si = 0; si < len; si++) - { - if (!isdigit(temp[si]) && L'/' != temp[si]) - alldigits = false; - } - } - if (alldigits) - { - // If this stacked run has only digits and a separator in the text, - // and the character before it is a digit, add a space to separate this - // from the previous run. The space is thrown away when parsing. - const wchar_t* s = strings.Array(); - int l = strings.Length(); - if (l > 0 && (isdigit(s[l - 1]) || (L'}' == s[l - 1] && isdigit(s[l - 2])))) - strings += L" "; - ComposeRun(run, dimstyle, fonttable, multiline, changecount, changefont, changecolor, bold, italic, underlined, strikeout, strings); - } - else // If it's not all digits, add [[ ]] -#endif - { - unsigned int facenamekey = GetFacenameKey(run_font, fonttable); - temp.Format(L"{\\f%d [[", facenamekey); - strings += temp; - - //strings += L"[["; - ComposeRun(run->m_stacked_text->m_top_run, dimstyle, fonttable, false, changecount, changefont, changecolor, bold, italic, underlined, strikeout, strings); - strings += L"/"; - ComposeRun(run->m_stacked_text->m_bottom_run, dimstyle, fonttable, false, changecount, changefont, changecolor, bold, italic, underlined, strikeout, strings); - strings += L"]]}"; - } - } - else - if (ON_TextRun::RunType::kField == run->Type()) - { - strings += L"%<"; - ComposeRun(run, dimstyle, fonttable, multiline, changecount, changefont, changecolor, bold, italic, underlined, strikeout, strings); - strings += L">%"; - } - else - ComposeRun(run, dimstyle, fonttable, multiline, changecount, changefont, changecolor, bold, italic, underlined, strikeout, strings); - } - } - else if (multiline && (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type())) - { - strings += L"{\\par}"; - } - } - } - - int nfont = fonttable.Count(); - int ncolor = colortable.Count(); - // Any time the font for the annotation's style has - // bold, italic or underline set, we have to write rtf - // even if there are no changes in the string itself - if (strings.Length() > 0 && - - (style_font.IsBoldInQuartet() || - // Currently not allowing italic or underlined in styles - // because the WPF text control doesn't deal with them well - //ON_Font::Style::Italic == style_font.FontStyle() || - //style_font.IsUnderlined() || - - 0 < changecount || - 1 < nfont || 0 < ncolor)) - { - // deff0 means use font0 for the default font throughout the string. - // If we include font0 in the font table, when we send - // the string back to the RTF control, the font listed as - // font0 will be used instead of the default font for the - // style. - // So the default font is listed as 0 and there is no - // entry in the table for font0. - - rtf.Format(L"{\\rtf1"); - - if (1 < nfont) - { - temp.Format(L"\\deff%d", deffont_index); - rtf += temp; - fonttbl_string = L"{\\fonttbl"; - for (int fi = 0; fi < nfont; fi++) - { - // - temp.Format(L"{\\f%d %s;}", fi, fonttable[fi]); - fonttbl_string += temp; - } - fonttbl_string += "}"; - rtf += fonttbl_string.Array(); - } - if (0 < ncolor) - { - colortbl = L"{\\colortbl"; - for (int ci = 0; ci < ncolor; ci++) - { - ON_Color color = colortable[ci]; - temp.Format(L"\\red%d,\\green%d\\blue%d;", ci, color.Red(), color.Green(), color.Blue()); - colortbl += temp; - } - colortbl += "}"; - rtf += colortbl.Array(); - } - - if (style_font.IsBoldInQuartet()) - rtf += L"{\\b"; - else - rtf += L"{\\b0"; - - - if (L'{' != strings[0]/* && ((1 == nfont && 0 == ncolor) || style_font.IsBoldInQuartet())*/) - rtf += L" "; - - rtf += strings; - - //if (style_font.IsBoldInQuartet()) - // rtf += L"}"; - - rtf += L"}}"; - } - else - { - rtf = strings; - } - return true; -} -#endif - diff --git a/opennurbs_textiterator.h b/opennurbs_textiterator.h index bde65a3e..3975ba99 100644 --- a/opennurbs_textiterator.h +++ b/opennurbs_textiterator.h @@ -862,42 +862,12 @@ public: }; - static bool ComposeA( - const ON_TextContent* text, - const ON_DimStyle* dimstyle, - ON_wString& rtf); - static bool Compose( const ON_TextContent* text, const ON_DimStyle* dimstyle, + const ON_wString default_fontname, ON_wString& rtf); - static void ComposeRunA( - const ON_TextRun* run, - const ON_DimStyle* dimstyle, - ON_SimpleArray< wchar_t[34] >& fonttable, - bool multiline, - int& changecount, - int& changefont, - bool& bold, - bool& italic, - bool& underlined, - RunInfo& runinfo); - - static void ComposeRun( - const ON_TextRun* run, - const ON_DimStyle* dimstyle, - ON_SimpleArray< wchar_t[34] >& fonttable, - bool multiline, - int& changecount, - int& changefont, - int& changecolor, - bool& bold, - bool& italic, - bool& underlined, - bool& strikeout, - ON_wString& strings_out); - static bool RecomposeRTF(); static void SetRecomposeRTF(bool b); diff --git a/opennurbs_textlog.cpp b/opennurbs_textlog.cpp index 214616ef..9028bae4 100644 --- a/opennurbs_textlog.cpp +++ b/opennurbs_textlog.cpp @@ -536,8 +536,12 @@ void ON_TextLog::Print( const ON_String& string ) void ON_TextLog::PrintString( const char* s ) { - if ( s && *s ) + if (s && *s) + { + if (m_beginning_of_line && m_indent.IsNotEmpty()) + AppendText(static_cast(m_indent)); AppendText(s); + } } void ON_TextLog::PrintNewLine() @@ -548,8 +552,12 @@ void ON_TextLog::PrintNewLine() void ON_TextLog::PrintString( const wchar_t* s ) { - if ( s && *s ) + if (s && *s) + { + if (m_beginning_of_line && m_indent.IsNotEmpty()) + AppendText(static_cast(m_indent)); AppendText(s); + } } void ON_TextLog::PrintRGB( const ON_Color& color ) diff --git a/opennurbs_textobject.cpp b/opennurbs_textobject.cpp index e208eecd..a0eb35af 100644 --- a/opennurbs_textobject.cpp +++ b/opennurbs_textobject.cpp @@ -229,6 +229,9 @@ bool ON_Text::Transform(const ON_Xform& xform, const ON_DimStyle* parent_dimstyl double oldheight = TextHeight(parent_dimstyle); double newheight = oldheight * scale; SetTextHeight(parent_dimstyle, newheight); + oldheight = MaskBorder(parent_dimstyle); + newheight = oldheight * scale; + SetMaskBorder(parent_dimstyle, newheight); } return rc; } diff --git a/opennurbs_unicode.cpp b/opennurbs_unicode.cpp index f6657553..6fc61bca 100644 --- a/opennurbs_unicode.cpp +++ b/opennurbs_unicode.cpp @@ -200,6 +200,27 @@ static int ON_IsUTF8ByteOrderMark( return (0xEF == (unsigned char)(sUTF8[0]) && 0xBB == (unsigned char)(sUTF8[1]) && 0xBF == (unsigned char)(sUTF8[2])); } +bool ON_IsUnicodeControlCodePoint( + ON__UINT32 code_point, + bool bNullReturnValue +) +{ + if (0 == code_point) + return bNullReturnValue ? true : false; + if (code_point < 0x0020) + return true; // below space + if (code_point < 0x007f) + return false; + if (code_point <= 0x00A0) + return true; // del to 0xA0 + if (code_point < 0x00AD) + return false; + if (code_point == 0x00AD) + return true; // soft hyphen + + return false; +} + int ON_EncodeUTF8( ON__UINT32 u, char sUTF8[6] ) { ON__UINT32 c; diff --git a/opennurbs_unicode.h b/opennurbs_unicode.h index 74d9f835..360074d3 100644 --- a/opennurbs_unicode.h +++ b/opennurbs_unicode.h @@ -3463,5 +3463,23 @@ bool ON_Test_PrintPlatformMSSBCPToUnicodeTable( ON_TextLog& text_log ); +/* +Parameters: + code_point - [in] + value to test + bNullReturnValue + value to return if 0 == code_point +Returns: + true if the code_point is a control code point + (> 0 && < U+0020) + or ( >= U+007F && <= U+00A0) + or ( == U+00AD) + or ... +*/ +ON_DECL +bool ON_IsUnicodeControlCodePoint( + ON__UINT32 code_point, + bool bNullReturnValue +); #endif #endif diff --git a/opennurbs_version.cpp b/opennurbs_version.cpp index 18fe815a..f1c5f90e 100644 --- a/opennurbs_version.cpp +++ b/opennurbs_version.cpp @@ -34,6 +34,16 @@ unsigned int ON::Version() return version_number; } +unsigned int ON::VersionMajorMaximum() +{ + return OPENNURBS_MAX_VERSION_MAJOR; +} + +unsigned int ON::VersionMinorMaximum() +{ + return OPENNURBS_MAX_VERSION_MINOR; +} + unsigned int ON::VersionMajor() { return OPENNURBS_VERSION_MAJOR; @@ -149,3 +159,138 @@ const wchar_t* ON::VersionQuartetAsWideString() { return OPENNURBS_VERSION_QUARTET_WSTRING; } + +bool ON_TestVersionNumber( + unsigned int major, + unsigned int minor, + unsigned int year, + unsigned int month, + unsigned int day_of_month, + unsigned int platform_branch, + unsigned int version_as_unsigned_number +) +{ + for (;;) + { + if (major < 1 || major > OPENNURBS_MAX_VERSION_MAJOR) + { + ON_ERROR("Invalid major parameter"); + break; + } + if (minor > OPENNURBS_MAX_VERSION_MINOR) + { + ON_ERROR("Invalid minor parameter"); + break; + } + if (year < 2000 || year >= 2099) + { + ON_ERROR("Invalid year parameter"); + break; + } + if (month < 1 || month > 12) + { + ON_ERROR("Invalid month parameter"); + break; + } + const unsigned int max_day_of_month = ON_DaysInMonthOfGregorianYear(year, month); + if (2 == month) + { + if (max_day_of_month != 28) + { + if (max_day_of_month != 29 || 0 != (year % 4)) + { + ON_ERROR("ON_DaysInMonthOfGregorianYear() has bug"); + break; + } + } + } + else + { + if (max_day_of_month != 30 && max_day_of_month != 31) + { + ON_ERROR("ON_DaysInMonthOfGregorianYear() has bug"); + break; + } + } + + if (day_of_month < 1 || day_of_month > max_day_of_month || max_day_of_month > 31) + { + ON_ERROR("Invalid day_of_month parameter"); + break; + } + + const unsigned int platform = (platform_branch > 0) ? (2 - (platform_branch % 2)) : 0; + + unsigned int version_major = 0xFFFFFFFFU; + unsigned int version_minor = 0xFFFFFFFFU; + unsigned int version_year = 0xFFFFFFFFU; + unsigned int version_month = 0xFFFFFFFFU; + unsigned int version_day_of_month = 0xFFFFFFFFU; + unsigned int version_platform = 0xFFFFFFFFU; + + if (0 == version_as_unsigned_number) + { + version_as_unsigned_number = ON_VersionNumberConstruct( + major, + minor, + year, + month, + day_of_month, + platform_branch + ); + } + + const bool bParsed = ON_VersionNumberParse( + version_as_unsigned_number, + &version_major, + &version_minor, + &version_year, + &version_month, + &version_day_of_month, + &version_platform + ); + if (false == bParsed) + { + ON_ERROR("Invalid version_as_unsigned_number parameter."); + break; + } + + if ( + version_major != major + || version_minor != minor + || version_year != year + || version_month != month + || version_day_of_month != day_of_month + || version_platform != platform + ) + { + ON_ERROR("version_as_unsigned_number does not encode version information."); + break; + } + + const unsigned int version_ctor_cpp = ON_VersionNumberConstruct( + major, + minor, + year, + month, + day_of_month, + platform_branch + ); + + if (version_ctor_cpp != version_as_unsigned_number) + { + ON_ERROR("version_as_unsigned_number != ON_VersionNumberConstruct()."); + break; + } + + const unsigned int version_ctor_macro = ON_VERSION_NUMBER_CTOR(major, minor, year, month, day_of_month, platform_branch); + if (version_ctor_macro != version_as_unsigned_number) + { + ON_ERROR("version_as_unsigned_number != ON_VERSION_NUMBER_CTOR()."); + break; + } + return true; + } + + return false; +} diff --git a/opennurbs_version.h b/opennurbs_version.h index dd3e7b27..82a5e297 100644 --- a/opennurbs_version.h +++ b/opennurbs_version.h @@ -44,6 +44,10 @@ #include "opennurbs_public_version.h" + +#define OPENNURBS_MAX_VERSION_MINOR 0x07F +#define OPENNURBS_MAX_VERSION_MAJOR 0x03F + //////////////////////////////////////////////////////////////// // // Major version number >= 0 and <= 63 @@ -68,19 +72,23 @@ //////////////////////////////////////////////////////////////// // -// branch = 0 to 3 +// branch => 0 // Use ON::VersionBranch() to get this value. // This number identifies the branch used in the build. // -// The build system automatically sets the value to -// 1, 2 or 3 before compiling any code. +// The build system automatically sets the value before compiling any code. // // The file checked into the source code repository // always has branch set to 0. -// 0 = developer build -// 1 = build system trunk build -// 2 = build system release candidate build -// 3 = build system release build +// +// RMA_VERSION_BRANCH is defined in opennurbs_version.h +// 0: developer build +// 1: Windows Commercial build +// 2: Mac Commercial build +// 3: Windows BETA build +// 4: Mac Beta build +// 5: Windows WIP build +// 6: Mac WIP build //#define OPENNURBS_VERSION_BRANCH 0 #define OPENNURBS_VERSION_BRANCH RMA_VERSION_BRANCH @@ -151,6 +159,27 @@ #define ON_VERSION_NUMBER_TIME(year, month, day_of_month) \ ((((year)-2000)*367) + (ON_VERSION_NUMBER_DAYOFYEAR(year,month,day_of_month))) +// branch is 0 for developer builds, odd for windows builds, and even for Mac builds +// When compressed into 2 bits, the type of build (Commercial/Beta/WIP) is not encoded. +// +// branch +// 0: developer build +// 1: Windows Commercial build +// 2: Mac Commercial build +// 3: Windows BETA build +// 4: Mac Beta build +// 5: Windows WIP build +// 6: Mac WIP build +// +// ON_VERSION_NUMBER_PLATFORM_ID(branch) = +// = 0: developer build +// 1: Windows build +// 2: Mac build +// +// NOTE WELL: +// ON_VERSION_NUMBER_PLATFORM_ID(branch) must be a value between 0 and 3. +#define ON_VERSION_NUMBER_PLATFORM_ID(branch) \ + (((branch) > 0x0U) ? (0x02U - ((branch) % 0x02U)) : 0x0U) //////////////////////////////////////////////////////////////// // @@ -160,7 +189,7 @@ // values. // // In almost every situation, it is best to used the function call -// ON_VersionNumberConstruct(major,minor,year,month,day_of_month) +// ON_VersionNumberConstruct(major,minor,year,month,day_of_month,branch) // to get this value. The ON_VERSION_NUMBER_CTOR macro is for // rare and unusual situations where the C preprocessor needs // this value. @@ -169,7 +198,7 @@ (0x80000000U \ + ((((major)*0x080U + (minor)))*0x010000U \ + ((ON_VERSION_NUMBER_TIME(year,month,day_of_month))))*0x04U \ - + ((branch))) + + ((ON_VERSION_NUMBER_PLATFORM_ID(branch)))) //////////////////////////////////////////////////////////////// // diff --git a/opennurbs_version_number.cpp b/opennurbs_version_number.cpp index 4089ba14..997da022 100644 --- a/opennurbs_version_number.cpp +++ b/opennurbs_version_number.cpp @@ -9,8 +9,6 @@ #endif #define BRANCH_MAX 0x03 -#define MINOR_MAX 0x07F -#define MAJOR_MAX 0x03F #define TIME_MAX 0x0FFFF @@ -86,7 +84,7 @@ unsigned int ON_VersionNumberConstruct( unsigned int year, unsigned int month, unsigned int day_of_month, - unsigned int branch + unsigned int platform_branch ) { unsigned int version_number = 0; @@ -94,13 +92,22 @@ unsigned int ON_VersionNumberConstruct( if (version_time > TIME_MAX) version_time = TIME_MAX; - if ( branch > BRANCH_MAX ) - branch = 0; // all invalid branch numbers get mapped to "developer" - if ( minor_version > MINOR_MAX) - minor_version = MINOR_MAX; - if ( major_version > MAJOR_MAX) - major_version = MAJOR_MAX; - version_number = ((major_version*(MINOR_MAX+1) + minor_version)*(TIME_MAX+1) + version_time)*(BRANCH_MAX+1) + branch; + + // platform + // 0 = developer + // 1 = Windows Commercial/Beta/WIP + // 2 = Mac OS Commercial/Beta/WIP + unsigned int platform + = (platform_branch > 0) + ? (2 - (platform_branch % 2) ) + : 0; + if ( platform > BRANCH_MAX ) + platform = 0; // all invalid branch numbers get mapped to "developer" + if ( minor_version > ON::VersionMinorMaximum()) + minor_version = ON::VersionMinorMaximum(); + if ( major_version > ON::VersionMajorMaximum()) + major_version = ON::VersionMajorMaximum(); + version_number = ((major_version*(ON::VersionMinorMaximum()+1) + minor_version)*(TIME_MAX+1) + version_time)*(BRANCH_MAX+1) + platform; version_number |= 0x80000000; return version_number; } @@ -201,6 +208,8 @@ bool ON_VersionNumberIsYearMonthDateFormat( } + + bool ON_VersionNumberIsValid( unsigned int version_number ) @@ -296,7 +305,7 @@ bool ON_VersionNumberParse( version_month, version_day_of_month); - max = MINOR_MAX+1; + max = ON::VersionMinorMaximum()+1; if ( 0 != version_minor ) *version_minor = u % max; u /= max; diff --git a/opennurbs_version_number.h b/opennurbs_version_number.h index 61b8754e..907f6e5a 100644 --- a/opennurbs_version_number.h +++ b/opennurbs_version_number.h @@ -38,12 +38,14 @@ Parameters: day_of_month - [in] day_of_month >= 1 and day_of_month <= ON_DaysInMonthOfGregorianYear(year,month) - branch - [in] - >= 0 and <= 3 - 0 = developer build - 1 = build system trunk branch build - 2 = build system release candidate branch build - 3 = build system release build + platform_branch - [in] + 0: developer build + 1: Windows Commercial build + 2: Mac Commercial build + 3: Windows BETA build + 4: Mac Beta build + 5: Windows WIP build + 6: Mac WIP build Returns: If the input values are valid, this returns @@ -65,7 +67,7 @@ unsigned int ON_VersionNumberConstruct( unsigned int year, unsigned int month, unsigned int day_of_month, - unsigned int branch + unsigned int platform_branch ); /* @@ -182,6 +184,42 @@ bool ON_VersionNumberParse( unsigned int* version_branch ); +/* +Description: + A tool to validate version information and to test opennurbs version number encoding and parsing. +Parameters: + major + A value between 1 and ON::VersionMajorMaximum(). + minor, + A value between 0 and ON::VersionMinorMaximum(). + year + A value between 2000 and 2099 + month + A value between 1 and 12 + day_of_month + A value between 1 and ON_DaysInMonthOfGregorianYear(year, month). + platform_branch + 0: developer build + >= 1: some type of build system build + version_as_unsigned_number + Either 0 or the encoded version number to test against the previous parameters. +Returns: + true + Input parameters are valid and testing internal version number utilities passed. + false + Inpute parameters are not valid or a bug was detected in internal version number utilities. +*/ +ON_DECL +bool ON_TestVersionNumber( + unsigned int major, + unsigned int minor, + unsigned int year, + unsigned int month, + unsigned int day_of_month, + unsigned int platform_branch, + unsigned int version_as_unsigned_number +); + ON_DECL const ON_String ON_VersionNumberToString( unsigned int version_number, diff --git a/opennurbs_win_dwrite.cpp b/opennurbs_win_dwrite.cpp index 0f9495f7..8923986b 100644 --- a/opennurbs_win_dwrite.cpp +++ b/opennurbs_win_dwrite.cpp @@ -1090,7 +1090,7 @@ unsigned int ON_Font::GetInstalledWindowsDWriteFonts( break; ON_FontGlyph glyph_box; glyph_box.m_code_point = codepoints[k]; - glyph_box.m_font_glyph_id = glyphIndex; + glyph_box.m_font_glyph_index = glyphIndex; // In font design units glyph_box.m_font_unit_glyph_bbox = ON_TextBox::CreateFromDWriteGlyphMetrics(&glyphMetric); @@ -1453,7 +1453,7 @@ bool ON_WindowsDWriteGetGlyphOutline( // is the default "TrueType" orientation. The Klavika OpenType font is // an example where the outlines returned by DirectWrite are "backwards" // from those returned by - accumulator.EndOutline(bNegatePointY,ON_OutlineFigure::Orientation::Clockwise); + accumulator.EndOutline(bNegatePointY, ON_Outline::DefaultOuterOrientation); DWRITE_GLYPH_METRICS glyphMetrics[2] = {}; @@ -1542,7 +1542,7 @@ void ON_WindowsDWriteGetFontMetrics( return; } -ON__UINT_PTR ON_WindowsDWriteGetGlyphMetrics( +unsigned int ON_WindowsDWriteGetGlyphMetrics( const ON_FontGlyph* glyph, ON_TextBox& glyph_metrics ) @@ -1563,14 +1563,14 @@ ON__UINT_PTR ON_WindowsDWriteGetGlyphMetrics( if (nullptr == dwriteFont) return 0; - ON__UINT_PTR glpyh_id = 0; + unsigned int glyph_index = 0; for(;;) { - glpyh_id - = glyph->FontGlyphIdIsSet() - ? glyph->FontGlyphId() + glyph_index + = glyph->FontGlyphIndexIsSet() + ? glyph->FontGlyphIndex() : 0; - if (glpyh_id > 0 && glpyh_id < 0xFFFFFFFF) + if (glyph_index > 0) break; Microsoft::WRL::ComPtr dwriteFontFace = nullptr; @@ -1587,18 +1587,18 @@ ON__UINT_PTR ON_WindowsDWriteGetGlyphMetrics( if (FAILED(hr)) break; - glpyh_id = glyphIndices[0]; + glyph_index = glyphIndices[0]; break; } const bool rc - = (0 != glpyh_id) - ? ON_WindowsDWriteGetGlyphMetrics(dwriteFont, (unsigned int)glpyh_id, glyph_metrics) + = (0 != glyph_index) + ? ON_WindowsDWriteGetGlyphMetrics(dwriteFont, glyph_index, glyph_metrics) : false; dwriteFont->Release(); - return rc ? glpyh_id : 0; + return rc ? glyph_index : 0; } bool ON_WindowsDWriteGetGlyphOutline( @@ -1615,10 +1615,7 @@ bool ON_WindowsDWriteGetGlyphOutline( if (false == glyph->CodePointIsSet()) return false; - ON__UINT64 glpyh_id = (ON__UINT64)glyph->FontGlyphId(); - if (glpyh_id > 0xFFFFFFFF) - return false; - const unsigned int dwriteGlyphIndex = (unsigned int)glpyh_id; + const unsigned int dwriteGlyphIndex = glyph->FontGlyphIndex(); const ON_Font* font = glyph->Font(); if (nullptr == font) diff --git a/opennurbs_wstring.cpp b/opennurbs_wstring.cpp index 31f9e0c2..28a42528 100644 --- a/opennurbs_wstring.cpp +++ b/opennurbs_wstring.cpp @@ -762,6 +762,132 @@ bool ON_wString::LoadResourceString(HINSTANCE instance, UINT id ) } #endif + +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) +ON_String::ON_String(CFStringRef appleString) +{ + Create(); + for (;;) + { + if (nullptr == appleString) + break; + + const char * utf8_str = CFStringGetCStringPtr(appleString, kCFStringEncodingUTF8); + ON_SimpleArray local_buffer; + if (nullptr == utf8_str) + { + CFIndex utf16_count = CFStringGetLength(appleString); + if (utf16_count <= 0) + break; + // get local storage + CFIndex utf8_capacity = 6*utf16_count; + local_buffer.Reserve((int)(utf8_capacity+1)); + local_buffer.SetCount((int)(utf8_capacity+1)); + local_buffer.Zero(); + Boolean b = CFStringGetCString(appleString, local_buffer.Array(), utf8_capacity, kCFStringEncodingUTF8); + if (b) + utf8_str = local_buffer.Array(); + if (nullptr == utf8_str) + break; + } + if ( 0 == utf8_str[0]) + break; + this->operator=(utf8_str); + break; + } +} + +ON_wString::ON_wString(CFStringRef appleString) +{ + Create(); + for (;;) + { + if (nullptr == appleString) + break; + CFIndex utf16_count = CFStringGetLength(appleString); + if (utf16_count <= 0) + break; + const UniChar * utf16_str = CFStringGetCharactersPtr(appleString); + ON_SimpleArray local_buffer; + if (nullptr == utf16_str) + { + // get local storage + local_buffer.Reserve((int)(utf16_count + 1)); + local_buffer.SetCount((int)(utf16_count + 1)); + CFRange range; + range.length = utf16_count; + range.location = 0; + CFStringGetCharacters(appleString, range, local_buffer.Array()); + local_buffer[(int)utf16_count] = 0; + utf16_str = local_buffer.Array(); + } + + ReserveArray(utf16_count); + if (2 == ON_SIZEOF_WCHAR_T) + { + for (CFIndex i = 0;i < utf16_count;i++) + m_s[i] = (wchar_t)(utf16_str[i]); + m_s[utf16_count] = 0; + Header()->string_length = utf16_count; + } + else + { + ON__UINT32 code_point; + int utf32_count = 0; + for (CFIndex i = 0;i < utf16_count;i++) + { + code_point = (ON__UINT32)(utf16_str[i]); + if ( + 0 == ON_IsValidUTF16Singleton(code_point) + && ( i+1 < utf16_count ) + && ON_IsValidUTF16SurrogatePair(code_point,utf16_str[i + 1]) + ) + { + code_point = ON_DecodeUTF16SurrogatePair(code_point, utf16_str[i + 1], ON_UnicodeCodePoint::ON_InvalidCodePoint); + if (ON_UnicodeCodePoint::ON_InvalidCodePoint != code_point) + i++; + else + code_point = (ON__UINT32)(utf16_str[i]); + } + m_s[utf32_count++] = (wchar_t)code_point; + } + m_s[utf32_count] = 0; + Header()->string_length = utf32_count; + } + break; + } +} + +CFStringRef ON_wString::ToAppleCFString() const +{ + if ( IsEmpty() || Length() <= 0) + return nullptr; + const ON_String utf8_string(*this); + return utf8_string.ToAppleCFString(); +} + +CFStringRef ON_String::ToAppleCFString() const +{ + for(;;) + { + if ( IsEmpty() || Length() <= 0 ) + break; + CFAllocatorRef alloc = nullptr; + const UInt8 *bytes = (UInt8 *)static_cast(*this); + if (nullptr == bytes || 0 == bytes[0]) + break; + CFIndex numBytes = (CFIndex)Length(); + CFStringEncoding encoding = kCFStringEncodingUTF8; + Boolean isExternalRepresentation = true; + CFStringRef appleString = CFStringCreateWithBytes( alloc, bytes, numBytes, encoding, isExternalRepresentation); + if (nullptr == appleString) + break; + return appleString; + } + return nullptr; +} +#endif + int ON_wString::Length() const { return Header()->string_length;