diff --git a/freetype263/opennurbs_public_freetype.xcodeproj/project.pbxproj b/freetype263/opennurbs_public_freetype.xcodeproj/project.pbxproj index 4d7ca33f..efb1762e 100644 --- a/freetype263/opennurbs_public_freetype.xcodeproj/project.pbxproj +++ b/freetype263/opennurbs_public_freetype.xcodeproj/project.pbxproj @@ -238,7 +238,7 @@ 1DB028251ED6433600FA9144 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1120; + LastUpgradeCheck = 1240; ORGANIZATIONNAME = "OpenNURBS 3dm File IO Toolkit"; TargetAttributes = { 1DB0282C1ED6433600FA9144 = { @@ -344,6 +344,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -401,6 +402,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; diff --git a/opennurbs_annotationbase.cpp b/opennurbs_annotationbase.cpp index a57850d0..9b9efd07 100644 --- a/opennurbs_annotationbase.cpp +++ b/opennurbs_annotationbase.cpp @@ -598,6 +598,7 @@ ON_3dVector ON_Annotation::GetDefaultHorizontal(const ON_Plane& plane) return ON_3dVector::XAxis; } + void ON_Annotation::CalcTextFlip( const ON_3dVector& text_xdir, const ON_3dVector& text_ydir, const ON_3dVector& text_zdir, const ON_3dVector& view_xdir, const ON_3dVector& view_ydir, const ON_3dVector& view_zdir, @@ -3326,7 +3327,7 @@ bool ON_Annotation::SetAnnotationFont(const ON_Font* font, const ON_DimStyle* pa textstring = dim->UserText(); ON_wString rtfstr(textstring); - const ON_wString newrtf = ON_TextContext::FormatRtfString(rtfstr, parent_style, !bold, bold, !italic, italic, false, false, false, true, fontname); + const ON_wString newrtf = ON_TextContext::FormatRtfString(rtfstr, parent_style, false, bold, false, italic, false, false, false, true, fontname); if (newrtf.IsNotEmpty()) { if (nullptr != dim) diff --git a/opennurbs_annotationbase.h b/opennurbs_annotationbase.h index a192536e..120ade37 100644 --- a/opennurbs_annotationbase.h +++ b/opennurbs_annotationbase.h @@ -192,7 +192,6 @@ public: bool& flip_x, bool& flip_y); - /* Returns: Rich text that can contain rich text formatting instructions. @@ -431,6 +430,11 @@ public: int end_run_pos); + // Deprecated - Use + // ON::TextVerticalAlignment ON_Annotation::TextVerticalAlignment(const ON_DimStyle* parent_style) const; + // void ON_Annotation::SetTextVerticalAlignment(const ON_DimStyle* parent_style, ON::TextVerticalAlignment style); + // ON::TextVerticalAlignment ON_Annotation::LeaderVerticalAlignment(const ON_DimStyle* parent_style) const; + // void ON_Annotation::SetLeaderVerticalAlignment(const ON_DimStyle* parent_style, ON::TextVerticalAlignment style); void GetAlignment(ON::TextHorizontalAlignment& horz, ON::TextVerticalAlignment& vert) const; void SetAlignment(ON::TextHorizontalAlignment horz, ON::TextVerticalAlignment vert); diff --git a/opennurbs_apple_nsfont.cpp b/opennurbs_apple_nsfont.cpp index 8c2cb855..f85ba3d8 100644 --- a/opennurbs_apple_nsfont.cpp +++ b/opennurbs_apple_nsfont.cpp @@ -26,10 +26,31 @@ #include "opennurbs_internal_glyph.h" #include "opennurbs_apple_nsfont.h" +class ON_AppleCTFontInformation +{ + // "fake class" (for now) +public: + ON_AppleCTFontInformation() = default; + ~ON_AppleCTFontInformation() = default; + ON_AppleCTFontInformation(const ON_AppleCTFontInformation&) = default; + ON_AppleCTFontInformation& operator=(const ON_AppleCTFontInformation&) = default; +private: + ON__UINT_PTR m_nothing = 0; +}; + +ON_Font::ON_Font( + ON_Font::FontType font_type, + const class ON_AppleCTFontInformation& apple_font_information +) +: m_font_type(font_type) +{} + void ON_ManagedFonts::Internal_GetAppleInstalledCTFonts( ON_SimpleArray& platform_font_list ) { + ON_AppleCTFontInformation currently_nothing; + CFDictionaryRef options = nullptr; CTFontCollectionRef availableFontCollection = CTFontCollectionCreateFromAvailableFonts(options); if (nullptr == availableFontCollection ) @@ -59,7 +80,9 @@ void ON_ManagedFonts::Internal_GetAppleInstalledCTFonts( continue; } - ON_Font* platform_font = new ON_Font(); + // It is critical that m_font_type = ON_Font::FontType::InstalledFont + ON_Font* platform_font = new ON_Font(ON_Font::FontType::InstalledFont,currently_nothing); + if (false == platform_font->SetFromAppleCTFont(font, false)) { CFRelease(font); @@ -514,6 +537,22 @@ CTFontRef ON_Font::AppleCTFont(bool& bIsSubstituteFont) const CTFontRef ON_Font::AppleCTFont(double pointSize, bool& bIsSubstituteFont) const { + if (this->IsManagedSubstitutedFont()) + { + // March 2021 Dale Lear + // Fixes RH-62974 on Mac platform + const ON_Font* sub =this->SubstituteFont(); + if (nullptr != sub &&sub->IsInstalledFont()) + { + if (false == sub->IsManagedSubstitutedFont()) + { + CTFontRef subref = sub->AppleCTFont( pointSize, bIsSubstituteFont); + bIsSubstituteFont = true; + return subref; + } + } + } + const bool bHavePointSize = ( pointSize > 0.0 ); const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 ); @@ -1174,346 +1213,6 @@ CTFontRef AppleTollFreeCTFont(NSFont* 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_archive.cpp b/opennurbs_archive.cpp index f08c2fe3..c382ef37 100644 --- a/opennurbs_archive.cpp +++ b/opennurbs_archive.cpp @@ -7593,14 +7593,33 @@ bool ON_BinaryArchive::WriteModelComponentName(const ON_ModelComponent & model_c return WriteString(valid_name); } -bool ON_BinaryArchive::Write3dmStartSection( int version, const char* sInformation ) +void ON_BinaryArchive::IntentionallyWriteCorrupt3dmStartSectionForExpertTesting() +{ + if (ON::archive_mode::write3dm == m_mode) + { + if (0 == m_IntentionallyWriteCorrupt3dmStartSection) + m_IntentionallyWriteCorrupt3dmStartSection = 1; // 1 meas a corrupt start section will be written. + else if (1 == m_IntentionallyWriteCorrupt3dmStartSection) + { + ON_ERROR("Please read the instructions in the header file."); + m_IntentionallyWriteCorrupt3dmStartSection = 2; // 2 indicates the "expert" tester goofed. + } + } + else + { + ON_ERROR("Please read the instructions in the header file."); + m_IntentionallyWriteCorrupt3dmStartSection = 2; // 2 indicates the "expert" tester goofed. + } +} + +bool ON_BinaryArchive::Write3dmStartSection(int version, const char* sStartSectionComment) { if (!Begin3dmTable(ON::archive_mode::write3dm,ON_3dmArchiveTableType::start_section)) return false; m_archive_runtime_environment = ON::CurrentRuntimeEnvironment(); - m_archive_3dm_start_section_comment = sInformation; + m_archive_3dm_start_section_comment = sStartSectionComment; if ( 0 == version ) version = ON_BinaryArchive::CurrentArchiveVersion(); @@ -7624,7 +7643,10 @@ bool ON_BinaryArchive::Write3dmStartSection( int version, const char* sInformati || (version >= 50 && 0 != (version % 10)) ) { - ON_ERROR("3dm archive version must be 2, 3, 4, 50 or 60"); + // 1, 2, 3, 4 use 32-bit chunk lengths. + // >=50 and a multiple of 10 use 64-bit chunk lengths. + // 64 bit chunk lengths were required in v5 to handle large mesh objects. + ON_ERROR("3dm archive version must be 2, 3, 4, 50, 60, 70, ..."); return End3dmTable(ON_3dmArchiveTableType::start_section,false); } @@ -7638,6 +7660,25 @@ bool ON_BinaryArchive::Write3dmStartSection( int version, const char* sInformati char sVersion[64]; memset( sVersion, 0, sizeof(sVersion) ); GetFirst32BytesOf3dmFile(version,sVersion); + + if (1 == m_IntentionallyWriteCorrupt3dmStartSection) + { + if (version == ON_BinaryArchive::CurrentArchiveVersion()) + { + m_IntentionallyWriteCorrupt3dmStartSection = 3; // 3 indicates the corrupt header was written. + // Change "3D Geometry File Format " + // to "3DXGeometryXFileXFormat " + sVersion[2] = 'X'; + sVersion[11] = 'X'; + sVersion[16] = 'X'; + } + else + { + // intentional corruption is requires working with the current version + m_IntentionallyWriteCorrupt3dmStartSection = 2; + } + } + if (!WriteByte( 32, sVersion )) return false; if (!BeginWrite3dmBigChunk( TCODE_COMMENTBLOCK, 0 )) @@ -7646,9 +7687,9 @@ bool ON_BinaryArchive::Write3dmStartSection( int version, const char* sInformati bool rc = false; for (;;) { - if ( sInformation && sInformation[0] ) + if (sStartSectionComment && sStartSectionComment[0] ) { - if (!WriteByte( strlen(sInformation), sInformation ) ) + if (!WriteByte( strlen(sStartSectionComment), sStartSectionComment) ) break; } // write information that helps determine what code wrote the 3dm file @@ -15697,6 +15738,13 @@ int ON_BinaryArchive::Read3dmObject( pAttributes->SetCustomRenderMeshParameters(ud->m_mp); delete ud; } + + //Strip out the $temp_object$ key left over from Block edit + auto* sl = ON_UserStringList::Cast(pAttributes->GetUserData(ON_CLASS_ID(ON_UserStringList))); + if (sl) + { + sl->SetUserString(L"$temp_object$", nullptr); + } #endif } } diff --git a/opennurbs_archive.h b/opennurbs_archive.h index 32b43426..2bcc47a6 100644 --- a/opennurbs_archive.h +++ b/opennurbs_archive.h @@ -3070,7 +3070,7 @@ public: /* Parameters: archive_3dm_version - [in] - 1,2,3,4,5,50,60,... + 1,2,3,4,5,50,60,70,... opennurbs_library_version - [in] a number > 100000000 */ @@ -3088,6 +3088,18 @@ public: // Step 1: REQUIRED - Write/Read Start Section // + + /* + Description: + In rare cases, experts testing handling of corrupt 3dm files need to + write a 3dm archive that is corrupt. In this rare testing situation, + those experts should call IntentionallyWriteCorrupt3dmStartSectionForExpertTesting() + exactly one time before they begin writing the file. The 32 byte idendifier will + replace the 1st 3 spaces with a capital X to mimic a file that became corrupt + whle residing on storage media. + */ + void IntentionallyWriteCorrupt3dmStartSectionForExpertTesting(); + /* Parameters: version - [in] @@ -4159,6 +4171,7 @@ public: 5 an old version 5 3dm archive is being read 50 a version 5 3dm archive is being read/written 60 a version 6 3dm archive is being read/written + 70 a version 7 3dm archive is being read/written ... See Also: ON_BinaryArchive::ArchiveOpenNURBSVersion @@ -4474,7 +4487,7 @@ private: int ReadObjectHelper(ON_Object**); int m_3dm_version = 0; // 1,2,3,4,5 (obsolete 32-bit chunk sizes) - // 50,60,... (64-bit chunk sizes) + // 50,60,70,... (64-bit chunk sizes) int m_3dm_v1_layer_index = 0; int m_3dm_v1_material_index = 0; @@ -5219,7 +5232,10 @@ public: private: bool m_SetModelComponentSerialNumbers = false; bool m_bCheckForRemappedIds = false; - bool m_reservedA = false; + // Expert testers who need to create a corrupt 3dm file + // call IntentionallyWriteCorrupt3dmStartSectionForExpertTesting() before writing + // the 3dm file. + unsigned char m_IntentionallyWriteCorrupt3dmStartSection = 0; bool m_reservedB = false; unsigned int m_model_serial_number = 0; unsigned int m_reference_model_serial_number = 0; @@ -5527,7 +5543,7 @@ public: false - Do not copy the input buffer. In this case you are responsible for making certain the input buffer is valid while this class is in use. - archive_3dm_version - [in] (1,2,3,4,5,50,60,...) + archive_3dm_version - [in] (1,2,3,4,5,50,60,70,...) archive_opennurbs_version - [in] */ ON_Read3dmBufferArchive( @@ -5601,7 +5617,7 @@ public: If max_sizeof_buffer > 0 and the amount of information saved requires a buffer larger than this size, then writing fails. If max_sizeof_buffer <= 0, then no buffer size limits are enforced. - archive_3dm_version - [in] (0, ,2,3,4,5,50,60,...) + archive_3dm_version - [in] (0, ,2,3,4,5,50,60,70,...) Pass 0 or ON_BinaryArchive::CurrentArchiveVersion() to write the version of opennurbs archives used by lastest version of Rhino. archive_opennurbs_version - [in] @@ -5688,7 +5704,7 @@ Description: Create a simple archive that contains a single or multiple geometric object(s). Parameters: archive - [in] destination archive. - version - [in] (0, 2, 3, 4,50,60,...) format version.archive version number. + version - [in] (0, 2, 3, 4,50,60,70,...) format version.archive version number. Version 2 format can be read by Rhino 2 and Rhino 3. Version 3 format can be read by Rhino 3. Pass 0 or ON_BinaryArchive::CurrentArchiveVersion() to write diff --git a/opennurbs_array.cpp b/opennurbs_array.cpp index f41de65c..eac04db6 100644 --- a/opennurbs_array.cpp +++ b/opennurbs_array.cpp @@ -84,6 +84,13 @@ ON_BoundingBox ON_3dPointArray::BoundingBox() const return bbox; } +ON_BoundingBox ON_3dPointArray::BoundingBox(int from, int count) const +{ + ON_BoundingBox bbox; + ON_GetPointListBoundingBox(3, false, count, 3, (m_a) ? &m_a[from].x : 0, &bbox.m_min.x, &bbox.m_max.x, false); + return bbox; +} + bool ON_3dPointArray::GetBoundingBox( ON_BoundingBox& bbox, int bGrowBox diff --git a/opennurbs_array.h b/opennurbs_array.h index 40efd3b3..63ad5677 100644 --- a/opennurbs_array.h +++ b/opennurbs_array.h @@ -201,6 +201,9 @@ public: int BinarySearch( const T*, int (*)(const T*,const T*) ) const; int BinarySearch( const T*, int (*)(const T*,const T*), int ) const; + int InsertInSortedList(const T&, int (*)(const T*, const T*)); + int InsertInSortedList(const T&, int (*)(const T*, const T*), int); + ////////// // Sorts the array using the heap sort algorithm. // QuickSort() is generally the better choice. @@ -554,6 +557,9 @@ public: int BinarySearch( const T*, int (*)(const T*,const T*) ) const; int BinarySearch( const T*, int (*)(const T*,const T*), int ) const; + int InsertInSortedList(const T&, int (*)(const T*, const T*)); + int InsertInSortedList(const T&, int (*)(const T*, const T*), int); + ////////// // Sorts the array using the heap sort algorithm. // See Also: ON_CompareIncreasing and ON_CompareDeccreasing diff --git a/opennurbs_array_defs.h b/opennurbs_array_defs.h index 754ef888..2a7cd965 100644 --- a/opennurbs_array_defs.h +++ b/opennurbs_array_defs.h @@ -761,7 +761,95 @@ int ON_SimpleArray::BinarySearch( const T* key, int (*compar)(const T*,const return rc; } +template +int ON_SimpleArray::InsertInSortedList(const T& e, int (*compar)(const T*, const T*)) +{ + const int count = m_count; + if (count < 0) + return -1; + if (0 == count) + { + Insert(0, e); + return 0; + } + const unsigned ucount = ((unsigned)count); + unsigned i0 = 0; + unsigned i1 = ucount; + while (i0 < i1) + { + const unsigned i = (i0 + i1) / 2; + const int c = compar(&e, m_a + i); + if (c < 0) + { + i1 = i; + } + else if (c > 0) + { + i0 = i + 1; + } + else + { + i1 = i; + while (i1 + 1 < ucount && 0 == compar(&e, m_a + (i1 + 1))) + ++i1; + i0 = i1; + } + } + if (i0 <= ucount) + { + Insert(i0, e); + return ((int)i0); + } + + return -1; +} + + +template +int ON_SimpleArray::InsertInSortedList(const T& e, int (*compar)(const T*, const T*), int count) +{ + if (count > m_count) + count = m_count; + if (count < 0) + return -1; + if (0 == count) + { + Insert(0, e); + return 0; + } + + const unsigned ucount = ((unsigned)count); + unsigned i0 = 0; + unsigned i1 = ucount; + while (i0 0) + { + i0 = i + 1; + } + else + { + i1 = i; + while (i1 + 1 < ucount && 0 == compar(&e, m_a + (i1 + 1))) + ++i1; + i0 = i1; + } + } + if (i0 <= ucount) + { + Insert(i0, e); + return ((int)i0); + } + + return -1; +} template bool ON_SimpleArray::HeapSort( int (*compar)(const T*,const T*) ) @@ -1736,6 +1824,98 @@ int ON_ClassArray::BinarySearch( const T* key, int (*compar)(const T*,const T return (nullptr != found && found >= m_a) ? ((int)(found - m_a)) : -1; } + +template +int ON_ClassArray::InsertInSortedList(const T& e, int (*compar)(const T*, const T*)) +{ + const int count = m_count; + if (count < 0) + return -1; + if (0 == count) + { + Insert(0, e); + return 0; + } + + const unsigned ucount = ((unsigned)count); + unsigned i0 = 0; + unsigned i1 = ucount; + while (i0 < i1) + { + const unsigned i = (i0 + i1) / 2; + const int c = compar(&e, m_a + i); + if (c < 0) + { + i1 = i; + } + else if (c > 0) + { + i0 = i + 1; + } + else + { + i1 = i; + while (i1 + 1 < ucount && 0 == compar(&e, m_a + (i1 + 1))) + ++i1; + i0 = i1; + } + } + if (i0 <= ucount) + { + Insert(i0, e); + return ((int)i0); + } + + return -1; +} + + +template +int ON_ClassArray::InsertInSortedList(const T& e, int (*compar)(const T*, const T*), int count) +{ + if (count > m_count) + count = m_count; + if (count < 0) + return -1; + if (0 == count) + { + Insert(0, e); + return 0; + } + + const unsigned ucount = ((unsigned)count); + unsigned i0 = 0; + unsigned i1 = ucount; + while (i0 < i1) + { + const unsigned i = (i0 + i1) / 2; + const int c = compar(&e, m_a + i); + if (c < 0) + { + i1 = i; + } + else if (c > 0) + { + i0 = i + 1; + } + else + { + i1 = i; + while (i1 + 1 < ucount && 0 == compar(&e, m_a + (i1 + 1))) + ++i1; + i0 = i1; + } + } + if (i0 <= ucount) + { + Insert(i0, e); + return ((int)i0); + } + + return -1; +} + + template bool ON_ClassArray::HeapSort( int (*compar)(const T*,const T*) ) { diff --git a/opennurbs_bounding_box.cpp b/opennurbs_bounding_box.cpp index 42f4517a..efb8a36c 100644 --- a/opennurbs_bounding_box.cpp +++ b/opennurbs_bounding_box.cpp @@ -1961,6 +1961,33 @@ bool ON_BoundingBox::IsDisjoint( const ON_BoundingBox& other_bbox ) const return false; } +bool ON_BoundingBox::IsDisjoint(const ON_Line& line) const +{ + return IsDisjoint(line, false); +} + +bool ON_BoundingBox::IsDisjoint(const ON_Line& line, bool infinite) const +{ + ON_3dPoint center = Center(); + ON_3dPoint halfdiag = Diagonal() * 0.5; + + ON_3dVector lc = line.PointAt(0.5) - center; + ON_3dVector halfdir = (line.to-line.from) * 0.5; + ON_3dVector absdir{ fabs(halfdir.x), fabs(halfdir.y), fabs(halfdir.z) }; + + if (!infinite && + (halfdiag.x + absdir.x < fabs(lc.x) || + halfdiag.y + absdir.y < fabs(lc.y) || + halfdiag.z + absdir.z < fabs(lc.z)) + ) + return true; + + ON_3dVector cross = ON_3dVector::CrossProduct(halfdir, lc); + return ((halfdiag.y * absdir.z + halfdiag.z * absdir.y < fabs(cross.x)) || + (halfdiag.x * absdir.z + halfdiag.z * absdir.x < fabs(cross.y)) || + (halfdiag.x * absdir.y + halfdiag.y * absdir.x < fabs(cross.z))); +} + bool ON_BoundingBox::Intersection( const ON_BoundingBox& a ) diff --git a/opennurbs_bounding_box.h b/opennurbs_bounding_box.h index 91b369c6..76aee6f2 100644 --- a/opennurbs_bounding_box.h +++ b/opennurbs_bounding_box.h @@ -558,6 +558,18 @@ public: const ON_BoundingBox& other_bbox ) const; + /* + Description: + Test to see if "this" and line are disjoint (do not intersect or line is included). + Parameters: + line - [in] + infinite - [in] if false or not provided, then the line is considered bounded by start and end points. + Returns: + True if "this" and line are disjoint. + */ + bool IsDisjoint(const ON_Line& line) const; + bool IsDisjoint(const ON_Line& line, bool infinite) const; + bool SwapCoordinates( int, int ); /* diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp index 410ee2fb..985f6471 100644 --- a/opennurbs_brep.cpp +++ b/opennurbs_brep.cpp @@ -6458,10 +6458,13 @@ bool ON_BrepFace::GetBBox( ON_Interval pudom(pbox[0].x, pbox[1].x); ON_Interval pvdom(pbox[0].y, pbox[1].y); - // fatten up invervals to get slightly larger boxes. + // fatten up invervals to get slightly larger boxes... pudom.Expand(.1 * pudom.Length()); pvdom.Expand(.1 * pvdom.Length()); ON_Interval Sdom[]= { Domain(0), Domain(1) }; + // but don't let the fattened intervals extend beyond Sdom + pudom.Intersection(Sdom[0]); + pvdom.Intersection(Sdom[1]); bool Used_pbox = false; if (pbox.IsValid() && (Sdom[0].Includes(pudom, true) || Sdom[1].Includes(pvdom, true))) diff --git a/opennurbs_curve.h b/opennurbs_curve.h index 5c078328..efe9a68a 100644 --- a/opennurbs_curve.h +++ b/opennurbs_curve.h @@ -1468,4 +1468,5 @@ double ON_CurveOrientationArea( bool bReverseCurve ); + #endif diff --git a/opennurbs_defines.h b/opennurbs_defines.h index e362b4be..5f646f20 100644 --- a/opennurbs_defines.h +++ b/opennurbs_defines.h @@ -124,12 +124,28 @@ #if defined(ON_COMPILER_MSC) #define ON_DEPRECATED __declspec(deprecated) #define ON_DEPRECATED_MSG(s) [[deprecated(s)]] +#if defined(OPENNURBS_IN_RHINO) +#define ON_WIP_SDK +#define ON_INTERNAL_SDK +#else +#define ON_WIP_SDK [[deprecated("Do not use! This function is a work in progress and will change.")]] +#define ON_INTERNAL_SDK [[deprecated("Do not use! This function is internal.")]] +#endif #elif defined(ON_COMPILER_CLANG) #define ON_DEPRECATED __attribute__((deprecated)) #define ON_DEPRECATED_MSG(s) [[deprecated(s)]] +#if defined(OPENNURBS_IN_RHINO) +#define ON_WIP_SDK +#define ON_INTERNAL_SDK +#else +#define ON_WIP_SDK [[deprecated("Do not use! This function is a work in progress and will change.")]] +#define ON_INTERNAL_SDK [[deprecated("Do not use! This function is internal.")]] +#endif #else #define ON_DEPRECATED #define ON_DEPRECATED_MSG(s) +#define ON_WIP_SDK +#define ON_INTERNAL_SDK #endif #if defined(PI) @@ -215,6 +231,13 @@ double ON_RadiansFromDegrees( #endif #define ON_SQRT_FLOAT_EPSILON 3.452669830725202719e-4 + +#if defined(UINT_MAX) +#define ON_UINT_MAX UINT_MAX +#else +#define ON_UINT_MAX (~(0U)) +#endif + /* // In cases where lazy evaluation of a double value is // performed, b-rep tolerances being a notable example, diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp index 141d6aa4..ce8be3be 100644 --- a/opennurbs_dimension.cpp +++ b/opennurbs_dimension.cpp @@ -199,7 +199,22 @@ ON_TextContent* ON_Dimension::RebuildDimensionText( } else { - displaytext = UserText(); + displaytext = displaytext + UserText(); + if (dimstyle->Prefix().IsNotEmpty() || dimstyle->Suffix().IsNotEmpty()) + { + int ci = displaytext.Find(L"<>"); + if (ci > -1) + { + ON_wString right; + if (displaytext.Length() > ci + 2) + right = displaytext.Right(displaytext.Length() - ci - 2); + displaytext = displaytext.Left(ci); + displaytext = displaytext + dimstyle->Prefix(); + displaytext = displaytext + L"<>"; + displaytext = displaytext + dimstyle->Suffix(); + displaytext = displaytext + right; + } + } } ON_TextContent* newtext = new ON_TextContent; @@ -690,6 +705,27 @@ bool ON_DimLinear::GetTextXform( double dimscale, ON_Xform& text_xform_out ) const +{ + ON_3dVector view_x = nullptr == vp ? ON_3dVector::XAxis : vp->CameraX(); + ON_3dVector view_y = nullptr == vp ? ON_3dVector::YAxis : vp->CameraY(); + ON_3dVector view_z = nullptr == vp ? ON_3dVector::ZAxis : vp->CameraZ(); + ON::view_projection projection = vp ? vp->Projection() : ON::view_projection::parallel_view; + bool bDrawForward = dimstyle == nullptr ? false : dimstyle->DrawForward(); + return GetTextXform(model_xform, view_x, view_y, view_z, projection, bDrawForward, dimstyle, dimscale, text_xform_out); +} + + +bool ON_DimLinear::GetTextXform( + const ON_Xform * model_xform, + const ON_3dVector view_x, + const ON_3dVector view_y, + const ON_3dVector view_z, + ON::view_projection projection, + bool bDrawForward, + const ON_DimStyle * dimstyle, + double dimscale, + ON_Xform & text_xform_out +) const { bool rc = false; if (nullptr == dimstyle) @@ -726,7 +762,7 @@ bool ON_DimLinear::GetTextXform( ON_Xform text_rotation(1.0); // Text rotation around text plane origin point // The amount past vertical where text flips to the other orientation - const double fliptol = (nullptr != vp && vp->Projection() == ON::view_projection::perspective_view) ? 0.0 : cos(80.001 * ON_DEGREES_TO_RADIANS); + const double fliptol = (projection == ON::view_projection::perspective_view) ? cos(89.0 * ON_DEGREES_TO_RADIANS) : cos(80.001 * ON_DEGREES_TO_RADIANS); ON_3dPoint text_center = ON_3dPoint::Origin; // Text starts out approximately centered at origin @@ -738,7 +774,11 @@ bool ON_DimLinear::GetTextXform( text_width = (cp[1].x - cp[0].x) * dimscale; text_height = (cp[3].y - cp[0].y) * dimscale; - text_gap = dimstyle->TextGap() * dimscale; + text_gap = dimstyle->TextGap(); + if (ON_TextMask::MaskFrame::RectFrame == dimstyle->MaskFrameType()) + text_gap = dimstyle->TextMask().MaskBorder(); + text_gap *= dimscale; + if (dimstyle->Alternate() && dimstyle->AlternateBelow()) text_height = -2.0 * text_gap; @@ -844,26 +884,22 @@ bool ON_DimLinear::GetTextXform( ON_3dVector dim_xaxis = Plane().xaxis; ON_3dVector dim_yaxis = Plane().yaxis; ON_3dVector dim_zaxis = Plane().zaxis; - if (nullptr != model_xform) + if (nullptr != model_xform && !model_xform->IsIdentity()) { dim_xaxis.Transform(*model_xform); dim_yaxis.Transform(*model_xform); dim_zaxis.Transform(*model_xform); } - ON_3dVector view_xdir = ON_3dVector::XAxis; - ON_3dVector view_ydir = ON_3dVector::YAxis; - ON_3dVector view_zdir = ON_3dVector::ZAxis; - if (nullptr != vp) - { - view_xdir = vp->CameraX(); - view_ydir = vp->CameraY(); - view_zdir = vp->CameraZ(); - } + ON_3dVector view_xdir = view_x; + ON_3dVector view_ydir = view_y; + ON_3dVector view_zdir = view_z; // text is in dimension plane, not horizontal to the view - ON_3dVector text_xdir = dim_xaxis; ON_2dVector h_dir = HorizontalDirection(); + ON_3dVector text_xdir = dim_xaxis; + ON_3dVector text_ydir = dim_yaxis; + ON_3dVector text_zdir = dim_zaxis; if (ON::TextOrientation::InPlane == text_orientation) { if (ON_DimStyle::ContentAngleStyle::Rotated == text_angle_style) @@ -875,19 +911,21 @@ bool ON_DimLinear::GetTextXform( { text_angle = 0.0; } + if (ON_DimStyle::ContentAngleStyle::Aligned != text_angle_style) { double h_angle = atan2(h_dir.y, h_dir.x); text_angle += h_angle; text_xdir.Rotate(h_angle, dim_zaxis); + text_ydir.Rotate(h_angle, dim_zaxis); } } - bool flip_x = false; + bool flip_x = false; bool flip_y = false; CalcTextFlip( - dim_xaxis, dim_yaxis, dim_zaxis, + text_xdir, text_ydir, text_zdir, view_xdir, view_ydir, view_zdir, model_xform, fliptol, @@ -2897,7 +2935,12 @@ bool ON_DimAngular::GetTextXform( text_xform_out = ON_Xform::IdentityTransformation; t2dxf.Rotation(textplane, dimplane); // Rotate text from starting text plane (world xy) to dimension plane - text_gap = dimstyle->TextGap() * dimscale; + + text_gap = dimstyle->TextGap(); + if (ON_TextMask::MaskFrame::RectFrame == dimstyle->MaskFrameType()) + text_gap = dimstyle->TextMask().MaskBorder(); + text_gap *= dimscale; + bool draw_forward = dimstyle->DrawForward(); ON_2dPoint text_pt_2d = TextPoint(); @@ -4064,7 +4107,11 @@ bool ON_DimRadial::GetTextXform( 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; + text_gap = dimstyle->TextGap(); + if (ON_TextMask::MaskFrame::RectFrame == dimstyle->MaskFrameType()) + text_gap = dimstyle->TextMask().MaskBorder(); + text_gap *= dimscale; + landing_length = dimstyle->LeaderLandingLength() * dimscale; ON_2dPoint dimline_pt = DimlinePoint(); diff --git a/opennurbs_dimension.h b/opennurbs_dimension.h index 4d71c71b..7e6f9c6a 100644 --- a/opennurbs_dimension.h +++ b/opennurbs_dimension.h @@ -446,6 +446,19 @@ public: bool from_the_back, ON_Xform& arrow_xform_out) const; +public: + bool GetTextXform( + const ON_Xform* model_xform, + const ON_3dVector view_x, + const ON_3dVector view_y, + const ON_3dVector view_z, + ON::view_projection projection, + bool bDrawForward, + const ON_DimStyle* dimstyle, + double dimscale, + ON_Xform& text_xform_out + ) const; + protected: ON_2dPoint m_def_pt_2 = ON_2dPoint::UnsetPoint; diff --git a/opennurbs_dimensionformat.cpp b/opennurbs_dimensionformat.cpp index 448dbe36..c350d614 100644 --- a/opennurbs_dimensionformat.cpp +++ b/opennurbs_dimensionformat.cpp @@ -426,7 +426,7 @@ bool ON_NumberFormatter::FormatAngleStringDMS(double angle_radians, int resoluti sign = -1; angle_degrees = -angle_degrees; } - //double minutes = (angle_degrees - floor(angle_degrees)) * 60.0; + angle_degrees = RoundOff(angle_degrees, 1e-8); double minutes = (angle_degrees - floor(angle_degrees)); minutes *= 60.0; double seconds = (minutes - floor(minutes)); @@ -457,11 +457,30 @@ bool ON_NumberFormatter::FormatAngleStringDMS(double angle_radians, int resoluti if (resolution == 2) { iseconds = (int)floor(seconds + 0.5); + if (iseconds >= 60) + { + iseconds -= 60; + iminutes += 1; + } + if (iminutes >= 60) + { + iminutes -= 60; + idegrees += 1; + } rc = formatted_string.Format(L"%d%lc %d\' %d\"", idegrees, ON_wString::DegreeSymbol, iminutes, iseconds); } else { - iseconds = (int)floor(seconds); + if (seconds >= 60.0) + { + seconds -= 60.0; + iminutes += 1; + } + if (iminutes >= 60) + { + iminutes -= 60; + idegrees += 1; + } ON_wString fmt; fmt.Format(L"%%d%%lc %%d\' %%.%dlf\"", resolution - 2); rc = formatted_string.Format(fmt.Array(), idegrees, ON_wString::DegreeSymbol, iminutes, seconds); diff --git a/opennurbs_dimensionstyle.cpp b/opennurbs_dimensionstyle.cpp index 42b7f5d7..80f202e4 100644 --- a/opennurbs_dimensionstyle.cpp +++ b/opennurbs_dimensionstyle.cpp @@ -2009,7 +2009,7 @@ const ON_DimStyle& ON_DimStyle::SystemDimstyleFromContentHash( const ON_SHA1_Hash& content_hash ) { - if (false == content_hash.IsZeroDigentOrEmptyContentHash()) + if (false == content_hash.IsZeroDigestOrEmptyContentHash()) { ON_SimpleArray system_dimstyle_list; const unsigned int count = ON_DimStyle::Internal_GetSystemDimstyleList(system_dimstyle_list); @@ -3544,11 +3544,20 @@ ON_DimStyle* ON_DimStyle::CreateFromFont( if ( model_view_text_scale > 0.0 && ON_IsValid(model_view_text_scale)) destination->SetDimScale(model_view_text_scale); - const ON_wString font_description = font_characteristics->Description(); + // Dale Lear RH-63824 May 3, 2021 + // It is critical that bIncludeNotOnDevice be set to false. + // Otherwise missing fonts will have a description beginning with "[Not on device]" + // and square brackets are not permitted in names. + // This code is inventing a Rhino 6/7 dimstyle name from a V4 text style. + // The text style names were unreliable in V4 and we've used the font + // description as a proxy for years now. + const bool bIncludeNotOnDevice = false; + + const ON_wString font_description = font_characteristics->Description(ON_Font::NameLocale::LocalizedFirst, ON_wString::HyphenMinus, ON_wString::Space, true, bIncludeNotOnDevice); if (font_description.IsNotEmpty()) { const ON_wString name - = (nullptr == manifest) + = (nullptr != manifest) ? manifest->UnusedName(ON_ModelComponent::Type::DimStyle, ON_nil_uuid, font_description, nullptr, nullptr, 0, nullptr) : font_description; destination->SetName(name); @@ -3754,6 +3763,111 @@ double ON_DimStyle::TextHeight() const return m_textheight; } +double ON_DimStyle::TextAdvanceOfCodePoint(unsigned unicode_code_point) const +{ + for (;;) + { + const double text_height = TextHeight(); + if (false == text_height > 0.0 && text_height < ON_UNSET_POSITIVE_VALUE) + break; + + const ON_Font& font = Font(); + const ON_FontGlyph* glyph = font.CodePointGlyph(unicode_code_point); + if (nullptr == glyph) + break; + + const ON_TextBox font_unit_bbox = glyph->FontUnitGlyphBox(); + const ON_TextBox normalized_bbox = glyph->GlyphBox(); + + const ON_FontMetrics& normalized_fm = font.FontMetrics(); + const ON_FontMetrics& font_unit_fm = font.FontUnitFontMetrics(); + + const int normalized_cap_height = normalized_fm.AscentOfCapital(); + const int normalized_glyph_advance = normalized_bbox.m_advance.i; // positive even for fonts designed for R to L locales + const int font_unit_cap_height = font_unit_fm.AscentOfCapital(); + const int font_unit_glyph_advance = font_unit_bbox.m_advance.i; // positive even for fonts designed for R to L locales + + const double normalized_scale = normalized_cap_height > 0 ? (((double)normalized_glyph_advance) / ((double)normalized_cap_height)) : 0.0; + const double font_unit_scale = font_unit_cap_height > 0 ? (((double)font_unit_glyph_advance) / ((double)font_unit_cap_height)) : 0.0; + + const double s = (font_unit_scale >= normalized_scale) ? font_unit_scale : normalized_scale; + + const double width_of_space = s * text_height; + if (width_of_space > 0.0 && width_of_space < ON_UNSET_POSITIVE_VALUE) + return width_of_space; + + break; // damaged or really odd font + } + + return 0.0; +} + +double ON_DimStyle::TextWidthOfEmSpace() const +{ + // This is the fundemental WidthOfXSpace() function. + // Other WidthOfXSpace() functions call TextWidthOfEmSpace() when TextAdvanceOfCodePoint(obvious code point) fails. + // This function may only call TextAdvanceOfCodePoint() and TextHeight(). + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_EmSpace); + if (false == w > 0.0) + { + w = TextAdvanceOfCodePoint('M'); + if (false == w > 0.0) + { + w = 2.0 * TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_EnSpace); + if (false == w > 0.0) + { + w = 2.0 * TextAdvanceOfCodePoint('N'); + if (false == w > 0.0) + { + w = 4.0 * TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_Space); + if (false == w > 0.0) + w = 1.5 * TextHeight(); + } + } + } + } + return w; +} + +double ON_DimStyle::TextWidthOfEnSpace() const +{ + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_EnSpace); + // Don't test 'N' in this function. + // It is critical that 2*TextWidthOfEnSpace() = TextWidthOfEmSpace() + // unless the font designer explicitly deviated from this standard. + return w > 0.0 ? w : 0.5 * TextWidthOfEmSpace(); +} + +double ON_DimStyle::TextWidthOfSpace() const +{ + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_Space); + return w > 0.0 ? w : 0.25 * TextWidthOfEmSpace(); +} + +double ON_DimStyle::TextWidthOfFigureSpace() const +{ + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_FigureSpace); + if (false == w > 0.0) + { + w = TextAdvanceOfCodePoint('0'); + if (false == w > 0.0) + w = 0.55 * TextWidthOfEmSpace(); + } + return w; +} + +double ON_DimStyle::TextWidthOfIdeographicSpace() const +{ + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_IdeographicSpace); + return w > 0.0 ? w : TextWidthOfEmSpace(); +} + +double ON_DimStyle::TextWidthOfMediumMathematicalSpace() const +{ + double w = TextAdvanceOfCodePoint(ON_UnicodeCodePoint::ON_MediumMathematicalSpace); + return w > 0.0 ? w : (2.0/9.0)*TextWidthOfEmSpace(); +} + void ON_DimStyle::SetTextHeight(double height) { if (ON_IsValid(height) && height > ON_SQRT_EPSILON) diff --git a/opennurbs_dimensionstyle.h b/opennurbs_dimensionstyle.h index 0bdd145e..6818e808 100644 --- a/opennurbs_dimensionstyle.h +++ b/opennurbs_dimensionstyle.h @@ -1391,6 +1391,70 @@ public: double TextHeight() const; void SetTextHeight(double height); + /* + Returns: + Width of an em space (U+2003), also called a mutton, in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + */ + double TextWidthOfSpace() const; + + /* + Returns: + Width of an em space (U+2003), also called a mutton, in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + */ + double TextWidthOfEmSpace() const; + + /* + Returns: + Width of an en space (U+2002), also called a nut, in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + */ + double TextWidthOfEnSpace() const; + + /* + Returns: + Width of a figure space (U+2007) in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + Remarks: + Typically close to the average with of a decimal digit (0123456789) and used + to line up colmns of numeric values. + */ + double TextWidthOfFigureSpace() const; + + /* + Returns: + Width of an ideographic space (U+3000) in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + Remarks: + The width of ideographic (CJK) characters. + */ + double TextWidthOfIdeographicSpace() const; + + /* + Returns: + Width of a medium mathematical space (U+205F) in the same units as TextHeight() + using the current settings for TextHeight() and Font(). + */ + double TextWidthOfMediumMathematicalSpace() const; + + /* + Parameters: + unicode_code_point - [in] + Returns: + The advande for a single code point glyph in the same units as TextHeight() using the current settings + for TextHeight() and Font(). + Remarks: + When sequences of code points are rendered, using the per glyph advance does not + create the best looking text. Text rendering tools like DirectWrite that look at the + entire string adjust advance based on the locale, neighboring glyphs, and other contextual + information. + */ + double TextAdvanceOfCodePoint( + unsigned unicode_code_point + ) const; + + /// /// LengthFactor is a rarely used. It applies when a model is being /// drawn to a scale and the dimension length values should be diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index a5c06a8b..bc3f6f7b 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -4614,7 +4614,7 @@ bool ONX_ModelTest::DumpModel(const ONX_Model* model, ON_TextLog& text_log) dump_hash.Dump(text_log); text_log.PrintNewLine(); - return (false == dump_hash.IsZeroDigentOrEmptyContentHash()); + return (false == dump_hash.IsZeroDigestOrEmptyContentHash()); } std::shared_ptr ONX_ModelTest::SourceModel() const diff --git a/opennurbs_file_utilities.cpp b/opennurbs_file_utilities.cpp index b790efdc..20e7e85d 100644 --- a/opennurbs_file_utilities.cpp +++ b/opennurbs_file_utilities.cpp @@ -564,9 +564,9 @@ const ON_wString ON_FileSystemPath::CurrentDirectory( #elif defined(ON_RUNTIME_APPLE) - // TODO implement for Apple OS's - ON_ERROR("ON_FileSystemPath::CurrentDirectory() not implemented."); - return ON_wString::EmptyString; + char sz[PATH_MAX]; + getcwd(sz, PATH_MAX); + return sz; #else diff --git a/opennurbs_font.cpp b/opennurbs_font.cpp index 53031897..23bff1d2 100644 --- a/opennurbs_font.cpp +++ b/opennurbs_font.cpp @@ -25,6 +25,56 @@ #include "opennurbs_win_dwrite.h" #include "opennurbs_apple_nsfont.h" +// Do not put this class in a public header file or SDK. +// It will be modified at unexpected times in unexpected ways. Any code outside of this file +// that uses the implementation will crash unexpectedly. +class ON_FontListImpl +{ +public: + ON_FontListImpl() = default; + ~ON_FontListImpl() = default; + +private: + ON_FontListImpl(const ON_FontListImpl&) = delete; + ON_FontListImpl& operator=(const ON_FontListImpl&) = delete; + +public: + // List of fonts sorted by FontCharacteristics hash + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_font_characteristics_hash; + + // List of fonts sorted by PostScript name + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_postscript_name; + + // List of fonts sorted by Windows LOGFONT.lfFaceName name + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_windows_logfont_name; + + // List of fonts sorted by Family name, then FaceName + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_family_name; + + + // List of fonts sorted by English PostScript name + // Only fonts with m_en_postscript_name != m_loc_postscript_name are included here + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_english_postscript_name; + + // List of fonts sorted by English Windows LOGFONT.lfFaceName name + // Only fonts with m_en_windows_logfont_name != m_loc_windows_logfont_name are included here + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_english_windows_logfont_name; + + // List of fonts sorted by English Family name, then English FaceName + // Only fonts with m_en_family_name != m_loc_family_name are included here + // (Recently added fonts may be in m_unsorted[]) + mutable ON_SimpleArray< const ON_Font* > m_by_english_family_name; + + mutable ON_SimpleArray< const ON_Font* > m_by_quartet_name; +}; + + ON_PANOSE1::FamilyKind ON_PANOSE1::FamilyKindFromUnsigned( unsigned int unsigned_panose_family_kind ) @@ -422,7 +472,7 @@ void ON_FontMetrics::SetAscentOfx( { m_ascent_of_x = (ascent_of_x > 0 && ascent_of_x <= 0xFFFF) ? ((unsigned short)ascent_of_x) : 0; } - + void ON_FontMetrics::SetStrikeout( int strikeout_position, int strikeout_thickness @@ -509,8 +559,9 @@ void ON_FontMetrics::SetAscentOfCapital( ) { int iascent_of_capital = Internal_FontMetricCeil(ascent_of_capital); - if (m_ascent < 0 && iascent_of_capital > m_ascent && iascent_of_capital <= m_ascent - 1) - iascent_of_capital = m_ascent; + //// Dale Lear Feb 2021 - Huh? The contition is never true unless m_ascent - 1 overflows to a positive number. + ////if (m_ascent < 0 && iascent_of_capital > m_ascent && iascent_of_capital <= m_ascent - 1) + //// iascent_of_capital = m_ascent; SetAscentOfCapital(iascent_of_capital); } @@ -519,8 +570,9 @@ void ON_FontMetrics::SetAscentOfx( ) { int iascent_of_x = Internal_FontMetricCeil(ascent_of_x); - if (m_ascent < 0 && iascent_of_x > m_ascent && iascent_of_x <= m_ascent - 1) - iascent_of_x = m_ascent; + //// Dale Lear Feb 2021 - Huh? The contition is never true unless m_ascent - 1 overflows to a positive number. + ////if (m_ascent < 0 && iascent_of_x > m_ascent && iascent_of_x <= m_ascent - 1) + //// iascent_of_x = m_ascent; SetAscentOfx(iascent_of_x); } @@ -603,8 +655,9 @@ const ON_FontMetrics ON_FontMetrics::Normalize( return ON_FontMetrics::Scale(font_metrics, scale); } -ON_ManagedFonts::ON_ManagedFonts() - : m_managed_fonts(true) +ON_ManagedFonts::ON_ManagedFonts(ON__UINT_PTR zero) + : m_default_font_ptr(zero) + , m_managed_fonts(true) , m_installed_fonts(false) {} @@ -920,6 +973,21 @@ const ON_Font* ON_ManagedFonts::GetFromFontCharacteristics( point_size ); + if (nullptr == managed_font && (bIsUnderlined || bIsStrikethrough)) + { + ON_Font undecorated(*set_font_characteristics); + undecorated.SetUnderlined(false); + undecorated.SetStrikethrough(false); + managed_font = m_managed_fonts.FromFontProperties( + &undecorated, + true, + true, + false, // ignore bIsUnderlined + false, // ignore bIsStrikethrough + point_size + ); + } + unsigned int managed_font_wss_dev = ON_Font::WeightStretchStyleDeviation( set_font_characteristics, managed_font @@ -1051,9 +1119,39 @@ const ON_Font* ON_ManagedFonts::Internal_AddManagedFont( const ON_Font* installed_font = InstalledFonts().FromFontProperties(managed_font, true, true); if (nullptr != installed_font) - managed_font->m_managed_face_is_installed = 1; + { + // This managed font matches an installed font. + ON_Font::Internal_SetManagedFontInstalledFont(managed_font, installed_font, false); + } else - managed_font->m_managed_face_is_installed = 2; + { + // Feb 22, 2021 RH-62974 + // This managed font is not installed on this device. + // After prototyping several approaches permitting variaous types + // of user configured font substitution, Lowell and Dale Lear + // concluded that using the default font was the best option. + // If this is a problem for lots of users, then we need a way + // for users to configure the choice of devault font. + // Current settings: + // Windows: Arial + // Apple: Helvetic Neue + + // managed_font is a missing font. + // We have to substitue the correct quartet member (regular/bold/italic/bold-italic). + // Since managed_font references a font that is not installed on this device, + // IsItalicInQuartet() and IsBoldInQuartet() will probably fall through to + // the fallback best guess sections of those functions. + const bool bBoldInQuartet = managed_font->IsBoldInQuartet(); + const bool bItalicInQuartet = managed_font->IsItalicInQuartet(); + if (ON_FontFaceQuartet::Member::Unset == managed_font->m_quartet_member) + managed_font->m_quartet_member = ON_FontFaceQuartet::MemberFromBoldAndItalic(bBoldInQuartet, bItalicInQuartet); + + const ON_FontFaceQuartet default_quartet = ON_Font::Default.InstalledFontQuartet(); // only look at installed quartet! + installed_font = default_quartet.ClosestFace(bBoldInQuartet, bItalicInQuartet); + if (nullptr == installed_font) + installed_font = &ON_Font::Default; + ON_Font::Internal_SetManagedFontInstalledFont(managed_font, installed_font, true); + } ON_FontGlyphCache* font_cache = managed_font->m_font_glyph_cache.get(); if (nullptr == font_cache) @@ -1590,6 +1688,8 @@ void ON_ManagedFonts::Internal_GetWindowsInstalledFonts( platform_font_list.Reserve(dwrite_font_list.Count()); + ON_SHA1_Hash prev_installed_font_hash = ON_SHA1_Hash::ZeroDigest; + for (int i = 0; i < dwrite_font_list.Count(); i++) { //const bool bSimulated @@ -1611,6 +1711,19 @@ void ON_ManagedFonts::Internal_GetWindowsInstalledFonts( for (;;) { ON_Font* installed_font = new ON_Font(ON_Font::FontType::InstalledFont, dwrite_font_list[i]); + + if ( + installed_font->WindowsLogfontName().IsEmpty() + && installed_font->PostScriptName().IsEmpty() + && installed_font->FamilyName().IsEmpty() + && installed_font->QuartetName().IsEmpty() + ) + { + // No reliable way to identify this font. + delete installed_font; + break; + } + platform_font_list.Append(installed_font); break; } @@ -1781,7 +1894,13 @@ bool ON_FontFaceQuartet::HasAllFaces() const bool ON_FontFaceQuartet::IsEmpty() const { - return (false==HasRegularFace() && false==HasBoldFace() && false==HasItalicFace() && false==HasBoldItalicFace()); + return IsNotEmpty() ? false : true; +} + + +bool ON_FontFaceQuartet::IsNotEmpty() const +{ + return (HasRegularFace() || HasBoldFace() || HasItalicFace() || HasBoldItalicFace()); } const ON_wString ON_FontFaceQuartet::QuartetName() const @@ -1927,6 +2046,43 @@ const ON_Font* ON_FontFaceQuartet::Face( : (bBold ? BoldFace() : RegularFace()); } +unsigned int ON_FontFaceQuartet::FaceCount() const +{ + unsigned count = 0; + const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; + for (unsigned i = 0; i < 4U; ++i) + { + if (nullptr != f[i]) + ++count; + } + return count; +} + +unsigned int ON_FontFaceQuartet::NotInstalledFaceCount() const +{ + unsigned count = 0; + const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; + for (unsigned i = 0; i < 4U; ++i) + { + if (nullptr != f[i] && f[i]->IsManagedSubstitutedFont()) + ++count; + } + return count; +} + +unsigned int ON_FontFaceQuartet::SimulatedFaceCount() const +{ + unsigned count = 0; + const ON_Font* f[4] = { m_regular,m_bold,m_italic,m_bold_italic }; + for (unsigned i = 0; i < 4U; ++i) + { + if (nullptr != f[i] && f[i]->IsSimulated()) + ++count; + } + return count; +} + + const ON_Font* ON_FontFaceQuartet::ClosestFace( bool bPreferedBold, bool bPreferedItalic @@ -1961,21 +2117,6 @@ const ON_Font* ON_FontFaceQuartet::ClosestFace( return m_bold_italic; } - -unsigned int ON_FontFaceQuartet::FaceCount() const -{ - unsigned int face_count = 0; - if (nullptr != RegularFace()) - face_count++; - if (nullptr != BoldFace()) - face_count++; - if (nullptr != ItalicFace()) - face_count++; - if (nullptr != BoldItalicFace()) - face_count++; - return face_count; -} - const ON_wString ON_FontFaceQuartet::MemberToString( ON_FontFaceQuartet::Member member ) @@ -2016,27 +2157,196 @@ ON_FontFaceQuartet::Member ON_FontFaceQuartet::MemberFromUnsigned( return ON_FontFaceQuartet::Member::Unset; } +ON_FontFaceQuartet::Member ON_FontFaceQuartet::MemberFromBoldAndItalic( + bool bMemberIsBold, + bool bMemberIsItalic + ) +{ + return + bMemberIsBold + ? (bMemberIsItalic ? ON_FontFaceQuartet::Member::BoldItalic : ON_FontFaceQuartet::Member::Bold) + : (bMemberIsItalic ? ON_FontFaceQuartet::Member::Italic : ON_FontFaceQuartet::Member::Regular) + ; +} + +unsigned ON_FontFaceQuartet::BoldItalicDeviation( + ON_FontFaceQuartet::Member desired_member, + ON_FontFaceQuartet::Member available_member +) +{ + if (desired_member == available_member) + return 0; + + unsigned distance = 0; + + if (ON_FontFaceQuartet::Member::Unset == desired_member) + { + distance += 4; + desired_member = ON_FontFaceQuartet::Member::Regular; + } + + if (ON_FontFaceQuartet::Member::Unset == available_member) + { + distance += 4; + available_member = ON_FontFaceQuartet::Member::Regular; + } + + const bool bDesiredBold = (ON_FontFaceQuartet::Member::Bold == desired_member || ON_FontFaceQuartet::Member::BoldItalic == desired_member); + const bool bDesiredItalic = (ON_FontFaceQuartet::Member::Italic == desired_member || ON_FontFaceQuartet::Member::BoldItalic == desired_member); + + const bool bAvailableBold = (ON_FontFaceQuartet::Member::Bold == available_member || ON_FontFaceQuartet::Member::BoldItalic == available_member); + const bool bAvailableItalic = (ON_FontFaceQuartet::Member::Italic == available_member || ON_FontFaceQuartet::Member::BoldItalic == available_member); + + if (bDesiredBold != bAvailableBold) + distance += 1; + if (bDesiredItalic != bAvailableItalic) + distance += 2; + + return distance; +} + +const ON_wString ON_Font::WidthWeightSlantDescription(ON_Font::Stretch width, ON_Font::Weight weight, ON_Font::Style slant) +{ + ON_wString wws; + if (ON_Font::Stretch::Unset != width) + { + if (wws.IsNotEmpty()) + wws += ON_wString(L"-"); + wws += ON_Font::StretchToWideString(width); + } + if (ON_Font::Weight::Unset != weight) + { + if (wws.IsNotEmpty()) + wws += ON_wString(L"-"); + wws += ON_Font::WeightToWideString(weight); + } + if (ON_Font::Style::Unset != slant) + { + if (wws.IsNotEmpty()) + wws += ON_wString(L"-"); + wws += ON_Font::StyleToWideString(slant); + } + return wws; +} + +const ON_wString ON_Font::WidthWeightSlantDescription() const +{ + return ON_Font::WidthWeightSlantDescription(this->FontStretch(), this->FontWeight(), this->FontStyle()); +} + void ON_FontFaceQuartet::Dump(ON_TextLog& text_log) const { - text_log.Print(L"Quartet Name: %ls\n", static_cast(m_quartet_name)); + ON_wString quartet_name = this->QuartetName(); + quartet_name.TrimLeftAndRight(); + if (this->IsEmpty() && quartet_name.IsEmpty()) + { + text_log.Print(L"Empty Quartet\n"); + return; + } + + const ON_wString quartet_face[4] = { + ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Regular), + ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Bold), + ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Italic), + ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::BoldItalic) + }; + const ON_Font* quartet_font[4] = { + RegularFace(), + BoldFace(), + ItalicFace(), + BoldItalicFace() + }; + const bool bFaceNotAvailable[4] = { + nullptr == quartet_font[0], + nullptr == quartet_font[1], + nullptr == quartet_font[2], + nullptr == quartet_font[3] + }; + const bool bFaceNotInstalled[4] = { + nullptr != quartet_font[0] && quartet_font[0]->IsManagedSubstitutedFont(), + nullptr != quartet_font[1] && quartet_font[1]->IsManagedSubstitutedFont(), + nullptr != quartet_font[2] && quartet_font[2]->IsManagedSubstitutedFont(), + nullptr != quartet_font[3] && quartet_font[3]->IsManagedSubstitutedFont() + }; + + const bool bQuartetNotAvailable + = bFaceNotAvailable[0] + && bFaceNotAvailable[1] + && bFaceNotAvailable[2] + && bFaceNotAvailable[3] + ; + const bool bQuartetNotInstalled + = (bFaceNotAvailable[0] || bFaceNotInstalled[0]) + && (bFaceNotAvailable[1] || bFaceNotInstalled[1]) + && (bFaceNotAvailable[2] || bFaceNotInstalled[2]) + && (bFaceNotAvailable[3] || bFaceNotInstalled[3]) + ; + + ON_wString quartet_decription(L"Quartet"); + if (quartet_name.IsNotEmpty()) + { + quartet_decription += ON_wString(L" "); + quartet_decription += quartet_name; + } + quartet_decription += ON_wString(L":"); + if (bQuartetNotAvailable) + { + quartet_decription += ON_wString(L" "); + } + else if (bQuartetNotInstalled) + { + quartet_decription += ON_wString(L" (not installed)"); + } + + text_log.PrintString(quartet_decription); + text_log.PrintNewLine(); text_log.PushIndent(); - const wchar_t* quartet_face[4] = { L"Regular",L"Bold",L"Italic",L"Bold-Italic" }; - const ON_Font* quartet_font[4] = { RegularFace(),BoldFace(),ItalicFace(),BoldItalicFace() }; for (int i = 0; i < 4; i++) { + ON_wString description(quartet_face[i]); + description += ON_wString(':'); const ON_Font* font = quartet_font[i]; if (nullptr == font) - text_log.Print(L"%ls: \n", quartet_face[i]); + description += ON_wString(L" "); else - text_log.Print( - L"%ls: %ls %ls (%ls) Weight = %ls Slope = %ls \n", - quartet_face[i], - static_cast(font->FamilyName()), - static_cast(font->FaceName()), - static_cast(font->PostScriptName()), - ON_Font::WeightToWideString(font->FontWeight()), - ON_Font::StyleToWideString(font->FontStyle()) - ); + { + const ON_wString family_name = font->FamilyName(); + if (family_name.IsNotEmpty()) + { + description += ON_wString(L" "); + description += family_name; + const ON_wString face_name = font->FaceName(); + if (face_name.IsNotEmpty()) + { + description += ON_wString(L" "); + description += face_name; + } + } + +#if defined(ON_RUNTIME_APPLE) + const ON_wString postscript_name = font->PostScriptName(); + if (postscript_name.IsNotEmpty()) + { + description += ON_wString(L" (PostScript name="); + description += postscript_name; + description += ON_wString(L")"); + } +#endif + + const ON_wString wws = font->WidthWeightSlantDescription(); + if (wws.IsNotEmpty()) + { + description += ON_wString(L" "); + description += wws; + } + + if (font->IsManagedSubstitutedFont()) + description += ON_wString(L" (not installed)"); + else if (font->IsInstalledFont() && font->IsSimulated()) + description += ON_wString(L" (simulated)"); + } + text_log.PrintString(description); + text_log.PrintNewLine(); } text_log.PopIndent(); } @@ -2255,10 +2565,11 @@ int ON_FontList::CompareUnderlinedStrikethroughPointSize( return 0; } -int ON_ManagedFonts::CompareFontCharacteristicsHash( - ON_Font const* const* lhs, + +int ON_FontList::CompareFontCharacteristicsHash( + ON_Font const* const* lhs, ON_Font const* const* rhs - ) +) { ON_ManagedFonts_CompareFontPointer(lhs, rhs); return ON_SHA1_Hash::Compare(lhs_font->FontCharacteristicsHash(), rhs_font->FontCharacteristicsHash()); @@ -3068,7 +3379,7 @@ const ON_Font* ON_Font::InstalledFontFromRichTextProperties( if (nullptr != installed_font) { - const ON_FontFaceQuartet q = installed_font->InstalledFontQuartet(); + const ON_FontFaceQuartet q = installed_font->InstalledFontQuartet(); // only look at installed quartet! const ON_Font* f = q.ClosestFace(bRtfBold, bRtfItalic); return (nullptr != f) ? f : installed_font; } @@ -3076,6 +3387,338 @@ const ON_Font* ON_Font::InstalledFontFromRichTextProperties( return nullptr; } +static void Internal_SetFakeNamesFromExistingNames( + ON_wString loc_existing, + ON_wString en_existing, + ON_wString& loc_fake, + ON_wString& en_fake +) +{ + loc_existing.TrimLeftAndRight(); + en_existing.TrimLeftAndRight(); + if (loc_existing.IsEmpty()) + loc_existing = en_existing; + else if (en_existing.IsEmpty()) + en_existing = loc_existing; + if (loc_existing.IsNotEmpty()) + loc_fake = loc_existing; + if (en_existing.IsNotEmpty()) + en_fake = en_existing; +} + + +const ON_Font* ON_Font::Internal_DecoratedFont( + bool bUnderlined, + bool bStrikethrough +) const +{ + // Underline and strikethrough are font rendering effects and are not designed into the font glyphs. + if (false == bUnderlined && false == bStrikethrough) + return this; + + if (bUnderlined ? 0 : 1 == this->IsUnderlined() ? 0 : 1 && bStrikethrough ? 0 : 1 == this->IsStrikethrough() ? 0 : 1) + return this; + + ON_Font decorated(*this); + decorated.SetUnderlined(bUnderlined); + decorated.SetStrikethrough(bStrikethrough); + const ON_Font* decorated_font = decorated.ManagedFont(); + if (nullptr != decorated_font && ON_FontFaceQuartet::Member::Unset == decorated_font->m_quartet_member) + { + // Decorated faces are not explicity in quartets, + // but when dealing with rich text, we need to know what quartet member they are decorating. + decorated_font->m_quartet_member = this->m_quartet_member; + } + + return (nullptr != decorated_font) ? decorated_font : this; +} + +const ON_Font* ON_Font::FontFromRichTextProperties( + ON_wString rich_text_font_name, + bool bBoldQuartetMember, + bool bItalicQuartetMember, + bool bUnderlined, + bool bStrikethrough +) +{ + rich_text_font_name.TrimLeftAndRight(); + if (rich_text_font_name.IsEmpty()) + rich_text_font_name = ON_Font::Default.RichTextFontName(); + + const ON_FontFaceQuartet::Member rich_text_quartet_face = ON_FontFaceQuartet::MemberFromBoldAndItalic(bBoldQuartetMember, bItalicQuartetMember); + + const bool bDecorated = bUnderlined || bStrikethrough; + + ON_FontFaceQuartet installed_quartet = ON_ManagedFonts::InstalledFonts().QuartetFromQuartetName(rich_text_font_name); + if ( installed_quartet.IsNotEmpty() ) + { + // Installed font quartets use ClosestFace() because we know all the faces + // the real font contains. + // If you don't get what you want, your asking for something that does not exist. + // You may need to fix some user interface to not offer the choice that led you here. + const ON_Font* installed_font = installed_quartet.ClosestFace(rich_text_quartet_face); // DO NOT CHANGE TO Face() + if (nullptr != installed_font) + return installed_font->Internal_DecoratedFont( bUnderlined, bStrikethrough); + } + + const ON_FontFaceQuartet managed_substitute_quartet = ON_ManagedFonts::ManagedFonts().QuartetFromQuartetName(rich_text_font_name); + if (managed_substitute_quartet.IsNotEmpty()) + { + // Missing font quartets use Face() because we don't know what the real font quartet looks like. + const ON_Font* managed_quartet_face = managed_substitute_quartet.Face(rich_text_quartet_face); // DO NOT CHANGE TO ClosestFace() + if (nullptr != managed_quartet_face) + return managed_quartet_face->Internal_DecoratedFont(bUnderlined, bStrikethrough); // this missing face has already been added to the quartet + } + + // We will need to make a new managed font and put it in a reasonable managed quartet. + + // All memory allocated for managed fonts is permanent app workspace memory. + ON_MemoryAllocationTracking disable_tracking(false); + { + // convert the name to untracked memory so it doesn't look like a leak + const ON_wString local_str(rich_text_font_name); + rich_text_font_name = ON_wString::EmptyString; + rich_text_font_name = ON_wString(static_cast(local_str)); + } + + const ON_Font* installed_font = nullptr; + installed_font = ON_Font::InstalledFontFromRichTextProperties(rich_text_font_name, bBoldQuartetMember, bItalicQuartetMember); + if (nullptr == installed_font) + { +#if defined(ON_RUNTIME_APPLE) + if (nullptr == installed_font) + installed_font = ON_Font::InstalledFontList().FromPostScriptName(rich_text_font_name); +#endif + if (nullptr == installed_font) + installed_font = ON_Font::InstalledFontList().FromWindowsLogfontName(rich_text_font_name); +#if !defined(ON_RUNTIME_APPLE) + if (nullptr == installed_font) + installed_font = ON_Font::InstalledFontList().FromPostScriptName(rich_text_font_name); +#endif + if (nullptr == installed_font) + installed_font = ON_Font::InstalledFontList().FromFamilyName(rich_text_font_name, ON_FontFaceQuartet::MemberToString(ON_FontFaceQuartet::Member::Regular) ); + if (nullptr != installed_font) + { + installed_quartet = installed_font->InstalledFontQuartet(); // only look at installed quartet! + const ON_Font* installed_rtfface = installed_quartet.ClosestFace(bBoldQuartetMember, bItalicQuartetMember); + if (nullptr != installed_rtfface) + installed_font = installed_rtfface; + } + } + + if (nullptr != installed_font) + { + // The font is installed on this device. + return installed_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); + } + + + ON_wString loc_quartet_name = rich_text_font_name; + ON_wString en_quartet_name = rich_text_font_name; + + ON_wString loc_family_name = rich_text_font_name; + ON_wString en_family_name = rich_text_font_name; + + ON_wString loc_windows_logfont_name = rich_text_font_name; + ON_wString en_windows_logfont_name = rich_text_font_name; + + ON_Font::Weight fake_normal_weight = ON_Font::Weight::Unset; + ON_Font::Weight fake_bold_weight = ON_Font::Weight::Unset; + + if (managed_substitute_quartet.IsNotEmpty()) + { + // get known names and guesses at weights from the existing parts of the quartet. + + const ON_Font* normal_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Regular); + if ( nullptr == normal_weight_font) + normal_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Italic); + + const ON_Font* bold_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::Bold); + if (nullptr == bold_weight_font) + bold_weight_font = managed_substitute_quartet.Face(ON_FontFaceQuartet::Member::BoldItalic); + + const ON_Font* names_font = (nullptr != normal_weight_font) ? normal_weight_font : bold_weight_font; + + if (nullptr != names_font) + { + Internal_SetFakeNamesFromExistingNames( + names_font->QuartetName(ON_Font::NameLocale::Localized), + names_font->QuartetName(ON_Font::NameLocale::English), + loc_quartet_name, + en_quartet_name + ); + + Internal_SetFakeNamesFromExistingNames( + names_font->FamilyName(ON_Font::NameLocale::Localized), + names_font->FamilyName(ON_Font::NameLocale::English), + loc_family_name, + en_family_name + ); + + Internal_SetFakeNamesFromExistingNames( + names_font->WindowsLogfontName(ON_Font::NameLocale::Localized), + names_font->WindowsLogfontName(ON_Font::NameLocale::English), + loc_windows_logfont_name, + en_windows_logfont_name + ); + } + + if (nullptr != normal_weight_font) + fake_normal_weight = normal_weight_font->FontWeight(); + if (nullptr != bold_weight_font) + fake_bold_weight = bold_weight_font->FontWeight(); + + const unsigned normal_unsigned = static_cast(fake_normal_weight); + const unsigned bold_unsigned = static_cast(fake_bold_weight); + const unsigned default_normal_unsigned = static_cast(ON_Font::Weight::Normal); + const unsigned default_bold_unsigned = static_cast(ON_Font::Weight::Bold); + const unsigned min_weight = static_cast(ON_Font::Weight::Thin); + const unsigned max_weight = static_cast(ON_Font::Weight::Heavy); + + if (ON_Font::Weight::Unset != fake_normal_weight && ON_Font::Weight::Unset == fake_bold_weight) + { + // have to guess at a bold weight + fake_bold_weight + = normal_unsigned < default_bold_unsigned + ? ON_Font::Weight::Bold + : ((normal_unsigned + 2 <= max_weight) ? ON_Font::FontWeightFromUnsigned(normal_unsigned + 2) : ON_Font::Weight::Heavy) + ; + } + + if (ON_Font::Weight::Unset != fake_bold_weight && ON_Font::Weight::Unset == fake_normal_weight) + { + // have to guess at a normal weight + fake_normal_weight + = bold_unsigned > default_normal_unsigned + ? ON_Font::Weight::Normal + : ((min_weight + 2 <= bold_unsigned) ? ON_Font::FontWeightFromUnsigned(bold_unsigned - 2) : ON_Font::Weight::Thin) + ; + } + } + + if (ON_Font::Weight::Unset == fake_normal_weight) + fake_normal_weight = ON_Font::Weight::Normal; + if (ON_Font::Weight::Unset == fake_bold_weight) + fake_bold_weight = ON_Font::Weight::Bold; + + const double point_size = 0.0; + const unsigned int logfont_charset = ON_Font::Default.LogfontCharSet(); + + ON_Font fake_font; + fake_font.SetFontCharacteristics( + point_size, + rich_text_font_name, + bBoldQuartetMember ? fake_bold_weight : fake_normal_weight, + bItalicQuartetMember ? ON_Font::Style::Italic : ON_Font::Style::Upright, + ON_Font::Stretch::Medium, + false, // DO NOT PASS bUnderlined here + false, // DO NOT PASS bStrikethrough here + ON_FontMetrics::DefaultLineFeedRatio, + logfont_charset + ); + + fake_font.m_loc_family_name = loc_family_name; + fake_font.m_en_family_name = en_family_name; + + fake_font.m_loc_windows_logfont_name = loc_windows_logfont_name; + fake_font.m_en_windows_logfont_name = en_windows_logfont_name; + + // There is no reliable way to get the face name - we fake it by using the quartet face name below + fake_font.m_loc_face_name = ON_FontFaceQuartet::MemberToString(rich_text_quartet_face); + fake_font.m_en_face_name = fake_font.m_loc_face_name; + + // There is no reliable way to get the PostScript name + fake_font.m_loc_postscript_name = ON_wString::EmptyString; + fake_font.m_en_postscript_name = ON_wString::EmptyString; + + fake_font.m_quartet_member = rich_text_quartet_face; + + // When reading 3dm files created on a device with other fonts + // Frequently a managed font is added when a dimstyle is read. + // Later on annotation objects are read and rich text requests + // regular/bold/italic/bold-italic faces from the dimstyle font's + // quartet. When existing_managed_font is not nullptr, we need + // to create a fake quartet containing that font. + // When existing_managed_font is nullptr, we need to create + // a fake quartet based on only the rtf_font_name. + // (Sure wish we had not dumped the V5 font table, sigh.) + const ON_Font* existing_managed_font = ON_Font::GetManagedFont(fake_font, false); + const ON_FontFaceQuartet::Member existing_managed_font_quartet_face = + (nullptr != existing_managed_font) + ? existing_managed_font->m_quartet_member + : ON_FontFaceQuartet::Member::Unset; + + if (nullptr != existing_managed_font) + { + Internal_SetFakeNamesFromExistingNames( + existing_managed_font->QuartetName(ON_Font::NameLocale::Localized), + existing_managed_font->QuartetName(ON_Font::NameLocale::English), + loc_quartet_name, + en_quartet_name + ); + + Internal_SetFakeNamesFromExistingNames( + existing_managed_font->FamilyName(ON_Font::NameLocale::Localized), + existing_managed_font->FamilyName(ON_Font::NameLocale::English), + loc_family_name, + en_family_name + ); + + Internal_SetFakeNamesFromExistingNames( + existing_managed_font->WindowsLogfontName(ON_Font::NameLocale::Localized), + existing_managed_font->WindowsLogfontName(ON_Font::NameLocale::English), + loc_windows_logfont_name, + en_windows_logfont_name + ); + + fake_font.m_loc_family_name = loc_family_name; + fake_font.m_en_family_name = en_family_name; + + fake_font.m_loc_face_name = ON_FontFaceQuartet::MemberToString(rich_text_quartet_face); + fake_font.m_en_face_name = fake_font.m_loc_face_name; + + fake_font.m_loc_windows_logfont_name = loc_windows_logfont_name; + fake_font.m_en_windows_logfont_name = en_windows_logfont_name; + } + + // Need to make a fake rich text quartet of managed fonts so rich text + // bold and italic faces work as expected. + + // creating a managed fake font resets the fake quartets + // and this fake will get added to the quartets next time + // they are needed. + const ON_Font* managed_fake_font = ON_Font::GetManagedFont(fake_font, true); + + if (nullptr == managed_fake_font) + { + // should never happen + return ON_Font::Default.Internal_DecoratedFont(bUnderlined, bStrikethrough); + } + + if (managed_fake_font->IsInstalledFont()) + { + return managed_fake_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); + } + + // set installed substitute used to render the missing font + const ON_Font* installed_substitute = managed_fake_font->SubstituteFont(); + if (nullptr == installed_substitute || false == installed_substitute->IsInstalledFont() || rich_text_quartet_face != installed_substitute->m_quartet_member) + { + // We have better information to select the correct substitute than inside the ON_Font::GetManagedFont(fake_font, true) call above. + // Use this information to specify a better substitute font. + installed_substitute = ON_Font::Default.InstalledFontQuartet().ClosestFace(rich_text_quartet_face); // only look at installed quartet + if (nullptr == installed_substitute) + installed_substitute = &ON_Font::Default; + const bool bInstalledFontIsASubstitute = true; + ON_Font::Internal_SetManagedFontInstalledFont( + managed_fake_font, + installed_substitute, + bInstalledFontIsASubstitute + ); + } + + return managed_fake_font->Internal_DecoratedFont(bUnderlined, bStrikethrough); +} const ON_wString ON_Font::RichTextPropertiesToString( bool bRtfBold, @@ -3121,25 +3764,6 @@ const ON_wString ON_Font::RichTextPropertiesToString( ); } -static unsigned int Internal_RtfDeviation( - const ON_Font* font, - bool bRtfBold, - bool bRtfItalic, - bool bRftUnderlined, - bool bRftStrikethrough -) -{ - if (nullptr == font) - return 0xFFFFFFFF; - - unsigned int bold_dev = (unsigned int)abs((int)(font->IsBold()?1:0) - (int)(bRtfBold?1:0)); - unsigned int italic_dev = (unsigned int)abs((int)(font->IsItalic()?1:0) - (int)(bRtfItalic?1:0)); - unsigned int undelined_dev = (unsigned int)abs((int)(font->IsUnderlined()?1:0) - (int)(bRftUnderlined?1:0)); - unsigned int strikethrough_dev = (unsigned int)abs((int)(font->IsStrikethrough()?1:0) - (int)(bRftStrikethrough?1:0)); - - return (8 * italic_dev + 4 * bold_dev + 2 * undelined_dev + 1 * strikethrough_dev); -} - const ON_Font* ON_Font::ManagedFontFromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, @@ -3148,119 +3772,10 @@ const ON_Font* ON_Font::ManagedFontFromRichTextProperties( bool bRftStrikethrough ) { - ON_wString s(rtf_font_name); - s.TrimLeftAndRight(); - if (s.IsEmpty()) - s = ON_Font::DefaultFamilyName(); - rtf_font_name = s; - - // insure exat true/false settings so we can compare - bRtfBold = bRtfBold ? true : false; - bRftStrikethrough = bRftStrikethrough ? true : false; - - const ON_Font* managed_font = ManagedFontList().FromRichTextProperties(rtf_font_name, bRtfBold, bRtfItalic, bRftUnderlined, bRftStrikethrough); - unsigned int managed_font_dev = Internal_RtfDeviation(managed_font, bRtfBold, bRtfItalic, bRftUnderlined, bRftStrikethrough); - if (nullptr != managed_font && managed_font_dev <= 3) - { - if (managed_font_dev > 0) - { - // add underlined and strikethrough settings - ON_Font font(*managed_font); - font.SetUnderlined(bRftUnderlined); - font.SetStrikethrough(bRftStrikethrough); - managed_font = font.ManagedFont(); - } - return managed_font; - } - - const ON_Font* installed_font = ON_Font::InstalledFontFromRichTextProperties(rtf_font_name, bRtfBold, bRtfItalic); - unsigned int installed_font_dev = Internal_RtfDeviation(installed_font, bRtfBold, bRtfItalic, bRftUnderlined, bRftStrikethrough); - if (nullptr != installed_font && installed_font_dev <= 3) - { - if (installed_font_dev > 0) - { - ON_Font font(*installed_font); - font.SetUnderlined(bRftUnderlined); - font.SetStrikethrough(bRftStrikethrough); - managed_font = font.ManagedFont(); - } - else - { - managed_font = installed_font->ManagedFont(); - } - return managed_font; - } - - if (nullptr != managed_font && managed_font_dev <= installed_font_dev) - return managed_font; // found something in the "rtf family/face" on this device or from a recently read model. - - if (nullptr != installed_font) - return installed_font->ManagedFont(); // found something in the "rtf family/face" on this device - - const ON_wString loc_family_name( - (nullptr != installed_font && installed_font->FamilyName().IsNotEmpty()) - ? installed_font->FamilyName() - : ON_wString(rtf_font_name) - ); - - const ON_wString en_family_name( - (nullptr != installed_font && installed_font->m_en_family_name.IsNotEmpty()) - ? installed_font->m_en_family_name - : loc_family_name - ); - - // There is not font is not installed on this device with any type of name that is equal to rtf_font_name - ON_Font font((nullptr != installed_font) ? (*installed_font) : ON_Font::Default); - - if (bRtfBold != font.IsBold()) - font.SetFontWeight(bRtfBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal); - if (bRtfItalic!= font.IsItalic()) - font.SetFontStyle(bRtfItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright); - if (bRftUnderlined) - font.SetUnderlined(bRftUnderlined); - if (bRftStrikethrough) - font.SetUnderlined(bRftStrikethrough); - - font.Internal_ClearAllNames(); - - ON_wString postscript_name = rtf_font_name; - ON_wString face_name; - if (bRtfBold && bRtfItalic) - { - postscript_name += L"-BoldItalic"; - face_name = L"Bold Italic"; - } - else if (bRtfBold) - { - postscript_name += L"-Bold"; - face_name = L"Bold"; - } - else if (bRtfItalic) - { - postscript_name += L"-Bold"; - face_name = L"Italic"; - } - else if (bRtfItalic) - { - face_name = L"Regular"; - } - - font.m_loc_family_name = loc_family_name; - font.m_en_family_name = en_family_name; - - // Best guess face name - font.m_loc_face_name = face_name; - font.m_en_face_name = font.m_loc_face_name; - - // Best guess PostScript name. - font.m_loc_postscript_name = postscript_name; - font.m_en_postscript_name = font.m_loc_postscript_name; - - // This is not correct, but works better than anything else, especially when saving as V5. - font.m_loc_windows_logfont_name = rtf_font_name; - font.m_en_windows_logfont_name = font.m_loc_windows_logfont_name; - - return font.ManagedFont(); + // ON_Font::ManagedFontFromRichTextProperties() is deprecated. + // ON_Font::FontFromRichTextProperties() is a single point source for + // converting rich text font properties into a managed font. + return ON_Font::FontFromRichTextProperties(rtf_font_name, bRtfBold, bRtfItalic, bRftUnderlined, bRftStrikethrough); } const ON_2dex ON_FontList::Internal_SearchSortedList( @@ -3296,13 +3811,14 @@ void ON_FontList::Internal_EmptyLists() { m_by_index.SetCount(0); m_unsorted.SetCount(0); - m_by_postscript_name.SetCount(0); - m_by_windows_logfont_name.SetCount(0); - m_by_family_name.SetCount(0); - m_by_english_postscript_name.SetCount(0); - m_by_english_windows_logfont_name.SetCount(0); - m_by_english_family_name.SetCount(0); - m_by_quartet_name.SetCount(0); + m_sorted.m_by_font_characteristics_hash.SetCount(0); + m_sorted.m_by_postscript_name.SetCount(0); + m_sorted.m_by_windows_logfont_name.SetCount(0); + m_sorted.m_by_family_name.SetCount(0); + m_sorted.m_by_english_postscript_name.SetCount(0); + m_sorted.m_by_english_windows_logfont_name.SetCount(0); + m_sorted.m_by_english_family_name.SetCount(0); + m_sorted.m_by_quartet_name.SetCount(0); m_quartet_list.Destroy(); } @@ -3320,7 +3836,11 @@ static int Internal_CompareLogfontNameEtc(ON_Font const* const* lhs, ON_Font con if (0 != rc) return rc; - return ON_ManagedFonts::CompareFontCharacteristicsHash(lhs, rhs); + rc = ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); + if (0 != rc) + return rc; + + return 0; } static int Internal_CompareFamilyNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) @@ -3356,7 +3876,7 @@ static int Internal_CompareEnglishLogfontNameEtc(ON_Font const* const* lhs, ON_F if (0 != rc) return rc; - return ON_ManagedFonts::CompareFontCharacteristicsHash(lhs, rhs); + return ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); } static int Internal_CompareEnglishFamilyNameEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) @@ -3392,7 +3912,17 @@ static int Internal_CompareQuartetNameEtc(ON_Font const* const* lhs, ON_Font con if (0 != rc) return rc; - return ON_ManagedFonts::CompareFontCharacteristicsHash(lhs, rhs); + return ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); +} + + +static int Internal_CompareFontCharacteristicsHashEtc(ON_Font const* const* lhs, ON_Font const* const* rhs) +{ + int rc = ON_FontList::CompareFontCharacteristicsHash(lhs, rhs); + if (0 != rc) + return rc; + + return Internal_CompareFamilyNameEtc(lhs, rhs); } void ON_FontList::Internal_UpdateSortedLists() const @@ -3401,18 +3931,19 @@ void ON_FontList::Internal_UpdateSortedLists() const if (unsorted_count <= 0) return; - ON_SimpleArray< const ON_Font* >* sorted_lists[7] = + ON_SimpleArray< const ON_Font* >* sorted_lists[8] = { - &m_by_postscript_name, - &m_by_windows_logfont_name, - &m_by_family_name, - &m_by_english_postscript_name, - &m_by_english_windows_logfont_name, - &m_by_english_family_name, - &m_by_quartet_name + &m_sorted.m_by_postscript_name, + &m_sorted.m_by_windows_logfont_name, + &m_sorted.m_by_family_name, + &m_sorted.m_by_english_postscript_name, + &m_sorted.m_by_english_windows_logfont_name, + &m_sorted.m_by_english_family_name, + &m_sorted.m_by_quartet_name, + &m_sorted.m_by_font_characteristics_hash, }; - ON_FontPtrCompareFunc compare_funcs[7] = + ON_FontPtrCompareFunc compare_funcs[8] = { Internal_ComparePostScriptNameEtc, Internal_CompareLogfontNameEtc, @@ -3422,7 +3953,9 @@ void ON_FontList::Internal_UpdateSortedLists() const Internal_CompareEnglishLogfontNameEtc, Internal_CompareEnglishFamilyNameEtc, - Internal_CompareQuartetNameEtc + Internal_CompareQuartetNameEtc, + + Internal_CompareFontCharacteristicsHashEtc }; const int array_dex_max = (int)(sizeof(sorted_lists) / sizeof(sorted_lists[0])); @@ -3485,6 +4018,16 @@ void ON_FontList::Internal_UpdateSortedLists() const // m_quartet_list[] will get remade when it's needed m_quartet_list.SetCount(0); } + else if (7 == array_dex) + { + const ON_SHA1_Hash sha1 = font->FontCharacteristicsHash(); + if (sha1.IsZeroDigestOrEmptyContentHash()) + continue; // no valid font wil have either one of these hashes. + } + else + { + ON_ERROR("When you add an array to ON_FontListImpl, you must add a corresponding if clause here."); + } sorted_list.Append(font); bNeedSort = true; @@ -3497,12 +4040,26 @@ void ON_FontList::Internal_UpdateSortedLists() const m_unsorted.SetCount(0); } +ON_FontList::ON_FontList() + : m_sorted(*(new ON_FontListImpl())) +{} + ON_FontList::ON_FontList( bool bMatchUnderlineAndStrikethrough ) : m_bMatchUnderlineStrikethroughAndPointSize(bMatchUnderlineAndStrikethrough) + , m_sorted(*(new ON_FontListImpl())) {} +ON_FontList::~ON_FontList() +{ + ON_FontListImpl* ptr = &m_sorted; + if (nullptr != ptr) + { + delete ptr; + } +} + unsigned int ON_FontList::Count() const { @@ -3514,6 +4071,50 @@ ON_Font::NameLocale ON_FontList::NameLocale() const return m_name_locale; } +const ON_Font* ON_FontList::FromFontCharacteristicsHash( + ON_SHA1_Hash font_characteristics_hash, + bool bReturnFirst) const +{ + if (font_characteristics_hash.IsZeroDigestOrEmptyContentHash()) + return nullptr; + + const ON_SimpleArray& by_hash = ByFontCharacteristicsHash(); + + // ON_FontList::CompareFontCharacteristicsHash() only looks at m_font_characteristics_hash + // and m_font_characteristics_hash must not be zero content hash (handled above). + const ON_Font keyf; + keyf.m_font_characteristics_hash = font_characteristics_hash; + const ON_Font* key = &keyf; + + const int i = by_hash.BinarySearch(&key, ON_FontList::CompareFontCharacteristicsHash); + const int count = by_hash.Count(); + if (i < 0 || i >= count) + return nullptr; + + int i0 = i; + while (i0 > 0 && 0 == ON_FontList::CompareFontCharacteristicsHash(&key, &by_hash[i0 - 1])) + --i0; + + int i1 = i; + while (i1 + 1 < count && 0 == ON_FontList::CompareFontCharacteristicsHash(&key, &by_hash[i1 + 1])) + ++i1; + + if (i0 == i1) + { + // The unique installed font with this hash. + return by_hash[i0]; + } + + if (bReturnFirst) + { + // the first of multiple fonts with this hash. + return by_hash[i0]; + } + + return nullptr; + +} + const ON_Font* ON_FontList::FromPostScriptName( const wchar_t* postscript_name @@ -3842,11 +4443,11 @@ const ON_Font* ON_FontList::Internal_FromNames( // fonts with the same family and face name combination. if (bKeyHasFamilyAndFace) { - sorted_lists[pass_count] = &m_by_family_name; + sorted_lists[pass_count] = &m_sorted.m_by_family_name; compare_funcs[pass_count] = ON_FontList::CompareFamilyAndFaceName; pass_count++; - sorted_lists[pass_count] = &m_by_english_family_name; + sorted_lists[pass_count] = &m_sorted.m_by_english_family_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishFamilyAndFaceName; pass_count++; } @@ -3856,11 +4457,11 @@ const ON_Font* ON_FontList::Internal_FromNames( // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. if (bKeyWindowsLogfontName) { - sorted_lists[pass_count] = &m_by_windows_logfont_name; + sorted_lists[pass_count] = &m_sorted.m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; pass_count++; - sorted_lists[pass_count] = &m_by_english_windows_logfont_name; + sorted_lists[pass_count] = &m_sorted.m_by_english_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishWindowsLogfontName; pass_count++; } @@ -3872,12 +4473,12 @@ const ON_Font* ON_FontList::Internal_FromNames( // It is less common in MacOS. if (bKeyHasPostScriptName) { - sorted_lists[pass_count] = &m_by_postscript_name; + sorted_lists[pass_count] = &m_sorted.m_by_postscript_name; compare_funcs[pass_count] = ON_FontList::ComparePostScriptName; postscript_name_pass[0] = pass_count; pass_count++; - sorted_lists[pass_count] = &m_by_english_postscript_name; + sorted_lists[pass_count] = &m_sorted.m_by_english_postscript_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishPostScriptName; postscript_name_pass[1] = pass_count; pass_count++; @@ -3888,11 +4489,11 @@ const ON_Font* ON_FontList::Internal_FromNames( // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. if (bKeyWindowsLogfontName) { - sorted_lists[pass_count] = &m_by_windows_logfont_name; + sorted_lists[pass_count] = &m_sorted.m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; pass_count++; - sorted_lists[pass_count] = &m_by_english_windows_logfont_name; + sorted_lists[pass_count] = &m_sorted.m_by_english_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishWindowsLogfontName; pass_count++; } @@ -3902,11 +4503,11 @@ const ON_Font* ON_FontList::Internal_FromNames( // It is generally the case that multiple faces have the same family name. if (key.m_loc_family_name.IsNotEmpty()) { - sorted_lists[pass_count] = &m_by_family_name; + sorted_lists[pass_count] = &m_sorted.m_by_family_name; compare_funcs[pass_count] = ON_FontList::CompareFamilyName; pass_count++; - sorted_lists[pass_count] = &m_by_english_family_name; + sorted_lists[pass_count] = &m_sorted.m_by_english_family_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishFamilyName; pass_count++; } @@ -4091,21 +4692,21 @@ unsigned int ON_FontList::FontListFromNames( case 0: if (key.m_loc_postscript_name.IsEmpty()) continue; - sorted_list = &m_by_postscript_name; + sorted_list = &m_sorted.m_by_postscript_name; subset = Internal_SearchSortedList(&key, ON_FontList::ComparePostScriptName, *sorted_list); break; case 1: if (key.m_loc_windows_logfont_name.IsEmpty()) continue; - sorted_list = &m_by_windows_logfont_name; + sorted_list = &m_sorted.m_by_windows_logfont_name; subset = Internal_SearchSortedList(&key, ON_FontList::CompareWindowsLogfontName, *sorted_list); break; case 2: if (key.m_loc_family_name.IsEmpty()) continue; - sorted_list = &m_by_family_name; + sorted_list = &m_sorted.m_by_family_name; subset = Internal_SearchSortedList(&key, ON_FontList::CompareFamilyName, *sorted_list); break; } @@ -4204,14 +4805,14 @@ const ON_Font* ON_FontList::FamilyMemberWithWeightStretchStyle( key.m_font_stretch = desired_stretch; key.m_font_style = desired_style; - const ON_2dex subdex = Internal_SearchSortedList(&key, ON_FontList::CompareFamilyName, m_by_family_name); + const ON_2dex subdex = Internal_SearchSortedList(&key, ON_FontList::CompareFamilyName, m_sorted.m_by_family_name); if (subdex.j <= 0) return nullptr; const ON_Font* candidate = nullptr; unsigned int candidate_dev = 0xFFFFFFFF; for (int i = subdex.i; i < subdex.j; i++) { - const ON_Font* font = m_by_family_name[i]; + const ON_Font* font = m_sorted.m_by_family_name[i]; if (nullptr == font) continue; unsigned int font_dev = ON_Font::WeightStretchStyleDeviation(&key, font); @@ -4246,7 +4847,7 @@ const ON_Font* ON_FontList::FamilyMemberWithWeightStretchStyle( ON_wString family_name = font->FamilyName(); if (family_name.IsEmpty()) { - const ON_SimpleArray< const ON_Font* > * sorted_lists[2] = {&m_by_windows_logfont_name,&m_by_postscript_name}; + const ON_SimpleArray< const ON_Font* > * sorted_lists[2] = { &m_sorted.m_by_windows_logfont_name, &m_sorted.m_by_postscript_name }; ON_FontPtrCompareFunc compare_funcs[2] = {ON_FontList::CompareWindowsLogfontName,ON_FontList::ComparePostScriptName}; const bool bNameIsEmpty[2] = { font->WindowsLogfontName().IsEmpty(),font->PostScriptName().IsEmpty() }; const int k1 = (int)(sizeof(bNameIsEmpty) / sizeof(bNameIsEmpty[0])); @@ -4296,56 +4897,20 @@ const ON_Font* ON_Font::ManagedFamilyMemberWithRichTextProperties( bool bStrikethrough ) const { - // Get clean boolean values for safe comparisons. - bBold = bBold ? true : false; - bItalic = bItalic ? true : false; - bUnderlined = bUnderlined ? true : false; - bStrikethrough = bStrikethrough ? true : false; - - const ON_Font::Weight current_weight = FontWeight(); - ON_Font::Weight desired_weight = current_weight; - if (bBold != IsBoldInQuartet()) - { - const ON_FontFaceQuartet q = InstalledFontQuartet(); - const ON_Font* f = nullptr; - if (bBold) - { - // increase desired_weight - f = (bItalic) ? q.BoldItalicFace() : q.BoldFace(); - if ( nullptr == f) - f = (bItalic) ? q.BoldFace() : q.BoldItalicFace(); - if (nullptr != f) - { - if (ON_Font::Weight::Unset == current_weight || static_cast(f->FontWeight()) > static_cast(current_weight)) - desired_weight = f->FontWeight(); - } - } - else - { - // descrease desired_weight - f = (bItalic) ? q.ItalicFace() : q.RegularFace(); - if ( nullptr == f) - f = (bItalic) ? q.RegularFace() : q.ItalicFace(); - if (nullptr != f) - { - if (ON_Font::Weight::Unset == current_weight || static_cast(f->FontWeight()) < static_cast(current_weight)) - desired_weight = f->FontWeight(); - } - } - } - - const ON_Font::Style desired_style - = (bItalic != IsItalic()) - ? (bItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright) - : FontStyle(); - - return ManagedFamilyMemberWithWeightStretchStyle( - desired_weight, - FontStretch(), - desired_style, - bUnderlined, - bStrikethrough - ); + // If this doesn't work, do not add code here. + // + // 1. If there's an obvious bug in ON_Font::ManagedFontFromRichTextProperties(), fix it. + // + // 2. If this is an installed font and the Platform is Mac and you don't like the results, + // then you probably need to hand tweak the fake Apple quartets we create in + // ON_ManagedFonts::Internal_SetFakeWindowsLogfontNames(). That code is Apple specific. + // + // 3. If this is an installed font and the Platform is Windows and you don't like the results, + // then your input font is probably generating a garbage for this->RichTextFontName() and + // that issue should be fixed upstream. + // + // 4. Ask Dale Lear for help. + return ON_Font::FontFromRichTextProperties(this->RichTextFontName(), bBold, bItalic, bUnderlined, bStrikethrough); } const ON_Font* ON_Font::ManagedFamilyMemberWithWeightStretchStyle( @@ -4424,25 +4989,31 @@ const ON_Font* ON_Font::ManagedFamilyMemberWithWeightStretchStyle( const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByPostScriptName() const { Internal_UpdateSortedLists(); - return m_by_postscript_name; + return m_sorted.m_by_postscript_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByWindowsLogfontName() const { Internal_UpdateSortedLists(); - return m_by_windows_logfont_name; + return m_sorted.m_by_windows_logfont_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByFamilyName() const { Internal_UpdateSortedLists(); - return m_by_family_name; + return m_sorted.m_by_family_name; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByQuartetName() const { Internal_UpdateSortedLists(); - return m_by_quartet_name; + return m_sorted.m_by_quartet_name; +} + +const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByFontCharacteristicsHash() const +{ + Internal_UpdateSortedLists(); + return m_sorted.m_by_font_characteristics_hash; } const ON_SimpleArray< const class ON_Font* >& ON_FontList::ByIndex() const @@ -4506,6 +5077,26 @@ const ON_Font* ON_FontList::FontFromQuartetProperties( return nullptr; } +ON_3udex Internal_StretchSlantWeightDex(unsigned max_stretch_dex, unsigned max_weight_dex, const ON_Font* f) +{ + for (;;) + { + if (nullptr == f) + break; + const unsigned stretch_dex = static_cast(f->FontStretch()); + if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) + break; + const unsigned slant_dex = (ON_Font::Style::Italic == f->FontStyle() || ON_Font::Style::Oblique == f->FontStyle()) + ? 1U + : 0U; + const unsigned weight_dex = static_cast(f->FontWeight()); + if (weight_dex < 1 || weight_dex >= max_weight_dex) + break; + return ON_3udex(stretch_dex, slant_dex, weight_dex); + } + return ON_3udex(ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX); +} + const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const { // call ByQuartetName() first to insure m_quartet_list[] is set correctly. @@ -4520,14 +5111,27 @@ const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const const ON_Font* f = nullptr; const unsigned max_stretch_dex = 10; const unsigned max_weight_dex = 10; - // quartet_fonts[stretch_dex][upright,italic][weight_dex] - // = all fonts in the quartet arranged by stretch, slant, and weight. - const ON_Font* quartet_fonts[11][2][11] = {}; - // count[stretch_dex][upright,italic] = number of weights available for that stretch and slant - unsigned int count[10][2] = {}; + // fonts_by_ssw[stretch_dex][upright,italic][weight_dex] + // = all fonts with the same quartet name arranged by stretch, slant, and weight. + const ON_Font* fonts_by_ssw[11][2][11] = {}; + + // weight_count[stretch_dex][upright,italic] = number of weights available for that stretch and slant + unsigned int weight_count[10][2] = {}; + + // stretch values range from min stretch = stretch_dex_range[0] to max stretch = stretch_dex_range[1]. unsigned stretch_dex_range[2] = { 0U,0U }; + // Fonts with stretch < medium_stretch_dex are "compressed" + // Fonts with stretch > medium_stretch_dex are "expanded" + // When we have multiple candidates to choose from, we opt for ones closest to medium. + const unsigned medium_stretch_dex = (unsigned)static_cast(ON_Font::Stretch::Medium); + + ON_SimpleArray regular_faces(8); + ON_SimpleArray bold_faces(8); + ON_SimpleArray italic_faces(8); + ON_SimpleArray bolditalic_faces(8); + unsigned next_i = 0U; for (unsigned i = 0; i < font_count; i = (i < next_i) ? next_i : (i+1)) { @@ -4539,31 +5143,84 @@ const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const if (quartet_name.IsEmpty()) continue; - memset(quartet_fonts, 0, sizeof(quartet_fonts)); - memset(count, 0, sizeof(count)); - const unsigned medium_stretch_dex = (unsigned)static_cast(ON_Font::Stretch::Medium); - unsigned stretch_dex = medium_stretch_dex; - unsigned slant_dex; - unsigned weight_dex; - unsigned quartet_count = 0; - for ( next_i = i; next_i < font_count; ++next_i) + // fonts_by_ssw_count = total number of undecorated fonts with the same QuartetName() + unsigned fonts_by_ssw_count = 0; + + // total number of fonts with underline or striketrough rendering effects enabled. + unsigned decorated_fonts_count = 0; + + // fonts_by_ssw[][][] = all the "clean" fonts with the same quartet name sorted by stretch-slant-weight + memset(fonts_by_ssw, 0, sizeof(fonts_by_ssw)); + memset(weight_count, 0, sizeof(weight_count)); + memset(stretch_dex_range, 0, sizeof(stretch_dex_range)); + regular_faces.SetCount(0); + bold_faces.SetCount(0); + italic_faces.SetCount(0); + bolditalic_faces.SetCount(0); + + unsigned int upright_face_count = 0; + unsigned int slanted_face_count = 0; + const ON_Font* upright_faces[2] = {}; // room to save up to 2 upright faces + const ON_Font* slanted_faces[2] = {}; // room to save up to 2 slanted faces + + // This for() loop sets the array limits for the fonts with the same quartet name + // and determines how many passes are needed (substitutes are ignored in favor of + // non substitutes). + unsigned pass_count = 1; + for (next_i = i; next_i < font_count; ++next_i) { f = a[next_i]; if (nullptr == f) break; if (false == quartet_name.EqualOrdinal(f->QuartetName(), true)) - break; + break; // f has a different quartet name - we're done getting the fonts in this quartet + if (f->IsManagedSubstitutedFont()) + pass_count = 2; + } + + // This for() loop gets all the fonts with the same QuartetName() and puts them in fonts_by_ssw[][][]. + // While doing that it get ranges of values stretch, counts the number of fonts that have each weight, ... + for ( unsigned pass = 0; pass < pass_count; ++pass) for ( unsigned j = i; j < next_i; ++j) + { + f = a[j]; + if (nullptr == f) + continue; + if (f->IsUnderlined() || f->IsStrikethrough()) + { + // these are rendering effects and we should have a "clean" version in this list too + ++decorated_fonts_count; + continue; + } + if (pass != (f->IsManagedSubstitutedFont() ? 1U : 0U)) + continue; - stretch_dex = static_cast(f->FontStretch()); + const ON_FontFaceQuartet::Member fm = f->m_quartet_member; + + // use f's stretch-slant-weight to add it to the fonts_by_ssw[][][] array. + const ON_3udex ssw_dex = Internal_StretchSlantWeightDex(max_stretch_dex, max_weight_dex, f); + + const unsigned stretch_dex = ssw_dex.i; if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) continue; - weight_dex = static_cast(f->FontWeight()); + + const unsigned slant_dex = ssw_dex.j; + + const unsigned weight_dex = ssw_dex.k; if (weight_dex < 1 || weight_dex >= max_weight_dex) continue; - slant_dex = f->IsItalicOrOblique() ? 1U : 0U; - if (nullptr != quartet_fonts[stretch_dex][slant_dex][weight_dex]) + + if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][weight_dex]) + { + // We already found one font with he same quartet name, stretch, slant, and weight. + // The first one wins and that's why we use two passes when substitute fonts are involved. continue; - if (0 == quartet_count) + } + + ++fonts_by_ssw_count; + fonts_by_ssw[stretch_dex][slant_dex][weight_dex] = f; + + // add this font's stretch, weight, and slant to the tally for this quartet name. + if (1 == fonts_by_ssw_count) { stretch_dex_range[0] = stretch_dex; stretch_dex_range[1] = stretch_dex; @@ -4572,105 +5229,355 @@ const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const stretch_dex_range[0] = stretch_dex; else if (stretch_dex > stretch_dex_range[1]) stretch_dex_range[1] = stretch_dex; - quartet_fonts[stretch_dex][slant_dex][weight_dex] = f; - ++count[stretch_dex][slant_dex]; - ++quartet_count; - } - if (0 == quartet_count) - continue; - if (stretch_dex_range[0] < stretch_dex_range[1]) - { - // Need to pick the stretch_dex with the most members. - // This happens on Mac OS (where no reliable "LOGFONT" name exists) and - // with damaged Windows fonts that don't have a "LOFGONT" name set. - // Pick the one closest to ON_Font::Stretch::Medium with the most faces - stretch_dex = medium_stretch_dex; - for (unsigned k = 1; k <= medium_stretch_dex; ++k) + ++weight_count[stretch_dex][slant_dex]; + + if (0 == slant_dex) { - const unsigned k0 = medium_stretch_dex - k; - const unsigned k1 = medium_stretch_dex + k; - if (k0 > 0 && (count[k0][0] + count[k0][1]) > (count[stretch_dex][0] + count[stretch_dex][1])) - stretch_dex = k0; - if (k1 < max_stretch_dex && (count[k1][0] + count[k1][1]) >(count[stretch_dex][0] + count[stretch_dex][1])) - stretch_dex = k1; + if (upright_face_count < 2) + upright_faces[upright_face_count] = f; + ++upright_face_count; } - } - else - stretch_dex = stretch_dex_range[0]; - - if (count[stretch_dex][0] + count[stretch_dex][1] <= 0) - continue; - - const unsigned normal_weight_dex = (unsigned)static_cast(ON_Font::Weight::Normal); - const unsigned medium_weight_dex = (unsigned)static_cast(ON_Font::Weight::Medium); - const unsigned bold_weight_dex = (unsigned)static_cast(ON_Font::Weight::Bold); - - const ON_Font* pairs[2][2] = {}; - for (slant_dex = 0; slant_dex < 2; slant_dex++) - { - if ( count[stretch_dex][slant_dex] <= 2 ) + else if (1 == slant_dex) { - // 0, 1 or 2 available weights. - // If there is 1 face it must be the "Regular" face. - // If there are 2 faces, the lightest will be "Regular" and the heaviest will be bold. - int pair_dex = 0; - for (int j = 1; j < max_weight_dex && pair_dex < 2; ++j) + if (slanted_face_count < 2) + slanted_faces[slanted_face_count] = f; + ++slanted_face_count; + } + + // if f's role in the quartet is known, add it to the appropriate x_faces[] array. + switch (fm) + { + case ON_FontFaceQuartet::Member::Regular: + regular_faces.Append(f); + break; + case ON_FontFaceQuartet::Member::Bold: + bold_faces.Append(f); + break; + case ON_FontFaceQuartet::Member::Italic: + italic_faces.Append(f); + break; + case ON_FontFaceQuartet::Member::BoldItalic: + bolditalic_faces.Append(f); + break; + }; + } + + // fonts_by_ssw_count = number of usable fonts with the same quartet name. + // Pointers to these fonts are someplace in the fonts_by_ssw[][][] array. + if (0 == fonts_by_ssw_count) + continue; // nothing usable + + // The goal is to look at the fonts in fonts_by_ssw[][][] and select + // the best choice for a rich text quartet (which may have 1 to 4 faces). + const ON_Font* quartet_faces[2][2] = {}; + bool bHaveQuartetFaces = false; + + const unsigned regular_count = regular_faces.UnsignedCount(); + const unsigned bold_count = bold_faces.UnsignedCount(); + const unsigned italic_count = italic_faces.UnsignedCount(); + const unsigned bolditalic_count = bolditalic_faces.UnsignedCount(); + if ( + fonts_by_ssw_count == (regular_count + bold_count + italic_count + bolditalic_count) + && regular_count <= 1 && bold_count <= 1 && italic_count <= 1 && bolditalic_count <= 1) + { + // Best case - every font with this quartet name knows the role it plays in the quartet and there are no contradictions. + quartet_faces[0][0] = 1 == regular_count ? regular_faces[0] : nullptr; + quartet_faces[0][1] = 1 == bold_count ? bold_faces[0] : nullptr; + quartet_faces[1][0] = 1 == italic_count ? italic_faces[0] : nullptr; + quartet_faces[1][1] = 1 == bolditalic_count ? bolditalic_faces[0] : nullptr; + bHaveQuartetFaces = true; + } + else if (fonts_by_ssw_count == upright_face_count + slanted_face_count + && upright_face_count <= 2 + && slanted_face_count <= 2 + && stretch_dex_range[0] == stretch_dex_range[1] + ) + { + if (2 == upright_face_count && ON_Font::CompareWeight(upright_faces[0]->FontWeight(), upright_faces[1]->FontWeight()) > 0) + { + f = upright_faces[0]; + upright_faces[0] = upright_faces[1]; + upright_faces[1] = f; + } + if (2 == slanted_face_count && ON_Font::CompareWeight(slanted_faces[0]->FontWeight(), slanted_faces[1]->FontWeight()) > 0) + { + f = slanted_faces[0]; + slanted_faces[0] = slanted_faces[1]; + slanted_faces[1] = f; + } + quartet_faces[0][0] = upright_faces[0]; + quartet_faces[0][1] = upright_faces[1]; + quartet_faces[1][0] = slanted_faces[0]; + quartet_faces[1][1] = slanted_faces[1]; + bHaveQuartetFaces = true; + } + + if (false == bHaveQuartetFaces) + { + // 1. Most Windows installed fonts have the Windows LOGFONT name reliably set + // from Windows LOGFONT information when we create installed ON_Fonts from + // DirectWrite fonts in opennurbs_win_dwrite.cpp. The Windows LOGFONTs partition + // families into quartets. These end up with bUsePairCandidate = true. + // + // 2. In rare cases on Windows, font foundaries or authors fail to specify a LOGFONT name + // in the ttc / ttf / postscript / ... file. + // If we are able to make a reasonable guess, then we set the member here. + // + // 3. Apple installed fonts are created from CTFont in opennurbs_apple_nsfont.cpp + // and the LOGFONT information from ttc / ttf / postscript files cannot be retrieved. + // There are no Mac OS tools that reliably paritition large font families into + // quartets. + // + // 4. Missing fonts that are created in ON_Font::FontFromRichTextProperties() have the quartet + // member set to the specified rich text regular/bold/italic/bold-italic property. + // + // Attempt to find something usable in this mess ... + + unsigned stretch_dex = medium_stretch_dex; + if (stretch_dex_range[0] < stretch_dex_range[1]) + { + // Need to pick the stretch_dex with the most members. + // This happens on Mac OS (where no reliable "LOGFONT" name exists) and + // with damaged Windows fonts that don't have a "LOFGONT" name set. + // Pick the one closest to ON_Font::Stretch::Medium with the most faces + for (unsigned k = 1; k <= medium_stretch_dex; ++k) { - if (nullptr != quartet_fonts[stretch_dex][slant_dex][j]) - pairs[slant_dex][pair_dex++] = quartet_fonts[stretch_dex][slant_dex][j]; + const unsigned k0 = medium_stretch_dex - k; + const unsigned k1 = medium_stretch_dex + k; + if (k0 > 0 && (weight_count[k0][0] + weight_count[k0][1]) > (weight_count[stretch_dex][0] + weight_count[stretch_dex][1])) + stretch_dex = k0; + if (k1 < max_stretch_dex && (weight_count[k1][0] + weight_count[k1][1]) >(weight_count[stretch_dex][0] + weight_count[stretch_dex][1])) + stretch_dex = k1; } + } + else + { + stretch_dex = stretch_dex_range[0]; + } + if (stretch_dex < 1 || stretch_dex >= max_stretch_dex) continue; - } - // 3 or more available weights (Bahnshrift, Helvetica Neue, ...) - unsigned regular_dex - = (nullptr != quartet_fonts[stretch_dex][slant_dex][normal_weight_dex]) - ? normal_weight_dex - : medium_weight_dex; - while (nullptr == quartet_fonts[stretch_dex][slant_dex][regular_dex] && regular_dex > 0) - --regular_dex; + if (weight_count[stretch_dex][0] + weight_count[stretch_dex][1] <= 0) + continue; - unsigned bold_dex - = (nullptr != quartet_fonts[stretch_dex][slant_dex][bold_weight_dex]) - ? bold_weight_dex - : regular_dex+1; - while (nullptr == quartet_fonts[stretch_dex][slant_dex][bold_dex] && bold_dex < max_weight_dex) - ++bold_dex; + const unsigned normal_weight_dex = (unsigned)static_cast(ON_Font::Weight::Normal); + const unsigned medium_weight_dex = (unsigned)static_cast(ON_Font::Weight::Medium); + const unsigned bold_weight_dex = (unsigned)static_cast(ON_Font::Weight::Bold); - if (nullptr != quartet_fonts[stretch_dex][slant_dex][regular_dex] && nullptr == quartet_fonts[stretch_dex][slant_dex][bold_dex] ) + for (unsigned slant_dex = 0; slant_dex < 2; slant_dex++) { - if (regular_dex > 0) + if (weight_count[stretch_dex][slant_dex] <= 2) { - for (unsigned j = regular_dex - 1; j > 0; --j) + // 0, 1 or 2 available weights. + // If there is 1 face it must be the "Regular" face. + // If there are 2 faces, the lightest will be "Regular" and the heaviest will be bold. + int pair_dex = 0; + for (int j = 1; j < max_weight_dex && pair_dex < 2; ++j) { - if (nullptr == quartet_fonts[stretch_dex][slant_dex][j]) - continue; - bold_dex = regular_dex; - regular_dex = j; - break; + if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][j]) + quartet_faces[slant_dex][pair_dex++] = fonts_by_ssw[stretch_dex][slant_dex][j]; + } + continue; + } + + // 3 or more available weights (Bahnshrift, Helvetica Neue, ...) + unsigned regular_dex + = (nullptr != fonts_by_ssw[stretch_dex][slant_dex][normal_weight_dex]) + ? normal_weight_dex + : medium_weight_dex; + while (nullptr == fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && regular_dex > 0) + --regular_dex; + + unsigned bold_dex + = (nullptr != fonts_by_ssw[stretch_dex][slant_dex][bold_weight_dex]) + ? bold_weight_dex + : regular_dex + 1; + while (nullptr == fonts_by_ssw[stretch_dex][slant_dex][bold_dex] && bold_dex < max_weight_dex) + ++bold_dex; + + if (nullptr != fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && nullptr == fonts_by_ssw[stretch_dex][slant_dex][bold_dex]) + { + if (regular_dex > 0) + { + for (unsigned j = regular_dex - 1; j > 0; --j) + { + if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][j]) + continue; + bold_dex = regular_dex; + regular_dex = j; + break; + } } } - } - else if (nullptr == quartet_fonts[stretch_dex][slant_dex][regular_dex] && nullptr != quartet_fonts[stretch_dex][slant_dex][bold_dex] ) - { - if (bold_dex > 0) + else if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][regular_dex] && nullptr != fonts_by_ssw[stretch_dex][slant_dex][bold_dex]) { - for (unsigned j = bold_dex - 1; j > 0; --j) + if (bold_dex > 0) { - if (nullptr == quartet_fonts[stretch_dex][slant_dex][j]) - continue; - regular_dex = j; - break; + for (unsigned j = bold_dex - 1; j > 0; --j) + { + if (nullptr == fonts_by_ssw[stretch_dex][slant_dex][j]) + continue; + regular_dex = j; + break; + } } } - } - pairs[slant_dex][0] = quartet_fonts[stretch_dex][slant_dex][regular_dex]; - pairs[slant_dex][1] = quartet_fonts[stretch_dex][slant_dex][bold_dex]; + quartet_faces[slant_dex][0] = fonts_by_ssw[stretch_dex][slant_dex][regular_dex]; + quartet_faces[slant_dex][1] = fonts_by_ssw[stretch_dex][slant_dex][bold_dex]; + } } - ON_FontFaceQuartet q(quartet_name,pairs[0][0],pairs[0][1],pairs[1][0],pairs[1][1]); + if (nullptr == quartet_faces[0][0] && nullptr == quartet_faces[0][1]) + { + // Fonts like Monotype Corsiva have only slanted faces. + // A quartet name + regular/bold/italic/italic-bold user interface should offer + // a regular and bold member in this situation. + quartet_faces[0][0] = quartet_faces[1][0]; + quartet_faces[0][1] = quartet_faces[1][1]; + quartet_faces[1][0] = nullptr; + quartet_faces[1][1] = nullptr; + } + + if (nullptr == quartet_faces[0][0] && nullptr == quartet_faces[1][0]) + { + // This might happen if buggy code encouters a heavy font like Arial Black + // and incorrectly specifies the heavy regular/italic faces as bold. + // A quartet name + regular/bold/italic/italic-bold user interface should offer + // a regular and italic member in this situation. + quartet_faces[0][0] = quartet_faces[0][1]; + quartet_faces[1][0] = quartet_faces[1][1]; + quartet_faces[0][1] = nullptr; + quartet_faces[1][1] = nullptr; + } + + // If ON_Font.m_quartet_member is not set or set incorrectly, + // then set it now so we get consistent answers going forward. + // (Managed quartets are recomputed as new missing fonts are added and in complex cases, the quartet member can change). + // Installed font quartet members are set once and never change. + const ON_FontFaceQuartet::Member member[2][2] = { + {ON_FontFaceQuartet::Member::Regular,ON_FontFaceQuartet::Member::Bold}, + {ON_FontFaceQuartet::Member::Italic,ON_FontFaceQuartet::Member::BoldItalic} + }; + for (int ii = 0; ii < 2; ++ii) for (int jj = 0; jj < 2; ++jj) + { + f = quartet_faces[ii][jj]; + if (nullptr != f) + { + const ON_FontFaceQuartet::Member m = f->m_quartet_member; + if (ON_FontFaceQuartet::Member::Unset == m) + f->m_quartet_member = member[ii][jj]; + else if (m != member[ii][jj]) + { + if (ON_Font::FontType::InstalledFont != f->m_font_type) + quartet_faces[ii][jj]->m_quartet_member = member[ii][jj]; + } + } + } + + // Unset the m_quartet_member on unsed fonts with this quartet name. + for (unsigned j = i; j < next_i; ++j) + { + f = a[j]; + if (nullptr == f) + continue; + if (f == quartet_faces[0][0] || f == quartet_faces[0][1] || f == quartet_faces[1][0] || f == quartet_faces[1][1]) + continue; + if (f->IsUnderlined() || f->IsStrikethrough()) + continue; // dealt with below + + const ON_FontFaceQuartet::Member fm = f->m_quartet_member; + if (ON_FontFaceQuartet::Member::Unset == fm) + continue; + if (ON_Font::FontType::InstalledFont != f->m_font_type) + continue; + + // m_quartet_member incorrectly set. + // If you are debugging and this is causing a problem, the bug is not here; + // it is in the code above that fills in quartet_faces[][]. + f->m_quartet_member = ON_FontFaceQuartet::Member::Unset; + } + + if (decorated_fonts_count > 0) + { + // This for() loop copies the clean font quartet settings to decorated (underlined and strikethrough) fonts + for (unsigned j = i; j < next_i; ++j) + { + f = a[j]; + if (nullptr == f) + continue; + if (false == f->IsUnderlined() && false == f->IsStrikethrough()) + continue; + + // f is underlined or strikethrough - find the clean version in fonts_by_ssw[][][] + const ON_3udex ssw_dex = Internal_StretchSlantWeightDex(max_stretch_dex, max_weight_dex, f); + const ON_Font* cleanf = ( + ssw_dex.i >= 1 && ssw_dex.i < max_stretch_dex + && ssw_dex.j >= 0 && ssw_dex.j < 2 + && ssw_dex.k >= 1 && ssw_dex.k < max_weight_dex + ) + ? fonts_by_ssw[ssw_dex.i][ssw_dex.k][ssw_dex.k] + : nullptr; + if (nullptr != cleanf) + { + const ON_FontFaceQuartet::Member fm = cleanf->m_quartet_member; + f->m_quartet_member = fm; + } + } + } + + // Now convert managed to installed when that makes sense. + for (int ii = 0; ii < 2; ++ii) for (int jj = 0; jj < 2; ++jj) + { + f = quartet_faces[ii][jj]; + if (nullptr == f) + continue; + if (ON_Font::FontType::InstalledFont == f->m_font_type) + continue; + if (f->IsManagedSubstitutedFont()) + continue; // common - f is a managed font that is missing from this device. + + if (false == f->IsManagedFont()) + continue; // troubling situation ... + if (f->IsUnderlined() || f->IsStrikethrough()) + continue; // troubling situation ... + + // There are 2 lists of fonts. + // All installed fonts - this list is made once when an application starts. + // Managed fonts - this list grows as an application needs fonts + // and is used to handle missing fonts and fonts with effects like underlined and strikethrough. + // + // This is a case where a managed font, like ON_Font::Default, + // the default engraving font, + // and more complicated cases that arise is rich text parsing, + // has an identical font that is installed. + const ON_Font* installed_font = f->Internal_ManagedFontToInstalledFont(); + if (nullptr == installed_font) + continue; + if (false == installed_font->IsInstalledFont()) + continue; // bad mojo happened sometime earlier in the life of this app instance. + if (false == quartet_name.EqualOrdinal(installed_font->QuartetName(), true)) + continue; // troubling situation ... + + // It is often the case that the installed quartet has faces not in the managed font list. + // ON_Font::Default is a good example. + // If there + ON_FontFaceQuartet installed_font_quartet = installed_font->InstalledFontQuartet(); + if (false == quartet_name.EqualOrdinal(installed_font_quartet.QuartetName(), true)) + continue; + + if (installed_font_quartet.HasRegularFace()) + quartet_faces[0][0] = installed_font_quartet.RegularFace(); + if (installed_font_quartet.HasBoldFace()) + quartet_faces[0][1] = installed_font_quartet.BoldFace(); + if (installed_font_quartet.HasItalicFace()) + quartet_faces[1][0] = installed_font_quartet.ItalicFace(); + if (installed_font_quartet.HasBoldItalicFace()) + quartet_faces[1][1] = installed_font_quartet.BoldItalicFace(); + } + + const ON_FontFaceQuartet q(quartet_name, quartet_faces[0][0], quartet_faces[0][1], quartet_faces[1][0], quartet_faces[1][1]); if (q.IsEmpty()) continue; @@ -4795,7 +5702,7 @@ bool ON_Font::IsInstalledFont() const rc = true; break; case ON_Font::FontType::ManagedFont: - rc = (1 == m_managed_face_is_installed); + rc = IsManagedInstalledFont(); break; default: rc = false; @@ -4804,6 +5711,56 @@ bool ON_Font::IsInstalledFont() const return rc; } +bool ON_Font::IsManagedInstalledFont() const +{ + const ON__UINT_PTR bits = 3; + return IsManagedFont() && (1 == (m_managed_installed_font_and_bits & bits)); +} + +bool ON_Font::IsManagedSubstitutedFont() const +{ + const ON__UINT_PTR bits = 3; + return IsManagedFont() && (2 == (m_managed_installed_font_and_bits & bits)); +} + +void ON_Font::Internal_SetManagedFontInstalledFont( + const ON_Font* managed_font, + const ON_Font* installed_font, + bool bInstalledFontIsASubstitute +) +{ + if (nullptr != managed_font) + { + ON__UINT_PTR x = 0; + if (nullptr != installed_font) + { + const ON__UINT_PTR bits = bInstalledFontIsASubstitute ? 2 : 1; + const ON__UINT_PTR ptr = (ON__UINT_PTR)installed_font; + x = ptr | bits; + } + managed_font->m_managed_installed_font_and_bits = x; + } +} + + +const ON_Font* ON_Font::SubstituteFont() const +{ + if (IsManagedSubstitutedFont()) + { + return Internal_ManagedFontToInstalledFont(); + } + return nullptr; +} + + +const ON_Font* ON_Font::Internal_ManagedFontToInstalledFont() const +{ + const ON__UINT_PTR bits = 3; + ON__UINT_PTR ptr = m_managed_installed_font_and_bits & (~bits); + return ((const ON_Font*)ptr); +} + + const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name ) @@ -5267,17 +6224,19 @@ void ON_Font::Internal_CopyFrom( m_font_stretch = bDefaultFont ? ON_Font::Stretch::Medium : ON_Font::Stretch::Unset; m_font_style = bDefaultFont ? ON_Font::Style::Upright : ON_Font::Style::Unset; - m_loc_family_name = (bDefaultFont ? ON_Font::DefaultFamilyName() : L""); - m_en_family_name = (bDefaultFont ? ON_Font::DefaultFamilyName() : L""); + m_loc_family_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFamilyName()) : ON_wString::EmptyString); + m_en_family_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFamilyName()) : ON_wString::EmptyString); - m_loc_face_name = (bDefaultFont ? ON_Font::DefaultFaceName() : L""); - m_en_face_name = (bDefaultFont ? ON_Font::DefaultFaceName() : L""); + m_loc_face_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFaceName()) : ON_wString::EmptyString); + m_en_face_name = (bDefaultFont ? ON_wString(ON_Font::DefaultFaceName()) : ON_wString::EmptyString); - m_loc_windows_logfont_name = (bDefaultFont ? ON_Font::DefaultWindowsLogfontName() : L""); - m_en_windows_logfont_name = (bDefaultFont ? ON_Font::DefaultWindowsLogfontName() : L""); + m_loc_windows_logfont_name = (bDefaultFont ? ON_wString(ON_Font::DefaultWindowsLogfontName()) : ON_wString::EmptyString); + m_en_windows_logfont_name = (bDefaultFont ? ON_wString(ON_Font::DefaultWindowsLogfontName()) : ON_wString::EmptyString); - m_loc_postscript_name = (bDefaultFont ? ON_Font::DefaultPostScriptName() : L""); - m_en_postscript_name = (bDefaultFont ? ON_Font::DefaultPostScriptName() : L""); + m_quartet_member = bDefaultFont ? ON_FontFaceQuartet::Member::Regular : ON_FontFaceQuartet::Member::Unset; + + m_loc_postscript_name = (bDefaultFont ? ON_wString(ON_Font::DefaultPostScriptName()) : ON_wString::EmptyString); + m_en_postscript_name = (bDefaultFont ? ON_wString(ON_Font::DefaultPostScriptName()) : ON_wString::EmptyString); m_font_bUnderlined = false; m_font_bStrikethrough = false; @@ -5313,16 +6272,16 @@ void ON_Font::Internal_CopyFrom( ) { if ( - ON_Font::FontType::ManagedFont == m_font_type - && m_runtime_serial_number > 0 - && 0 == m_managed_face_is_installed) + ON_Font::FontType::ManagedFont == this->m_font_type + && this->m_runtime_serial_number > 0 + && 0 == this->m_managed_installed_font_and_bits) { // When 1 == m_runtime_serial_number, this font is ON_Font::Default // and its face is installed on this device. Otherwise // this is a managed font being created by some other process. // // See RH-58472 for rare cases when this is required. (A V5 file being read at Rhino startup). - m_managed_face_is_installed = 1; + ON_Font::Internal_SetManagedFontInstalledFont(this, installed_font, false); } // Set stretch from installed font. @@ -5350,6 +6309,11 @@ void ON_Font::Internal_CopyFrom( if ( installed_font->m_en_windows_logfont_name.IsNotEmpty() ) m_en_windows_logfont_name = installed_font->m_en_windows_logfont_name; + if (m_loc_windows_logfont_name.IsNotEmpty() || m_en_windows_logfont_name.IsNotEmpty()) + m_quartet_member = installed_font->m_quartet_member; + else + m_quartet_member = ON_FontFaceQuartet::Member::Unset; + m_logfont_charset = installed_font->m_logfont_charset; #endif @@ -5388,6 +6352,11 @@ void ON_Font::Internal_CopyFrom( m_loc_windows_logfont_name = src.m_loc_windows_logfont_name; m_en_windows_logfont_name = src.m_en_windows_logfont_name; + if (m_loc_windows_logfont_name.IsNotEmpty() || m_en_windows_logfont_name.IsNotEmpty()) + m_quartet_member = src.m_quartet_member; + else + m_quartet_member = ON_FontFaceQuartet::Member::Unset; + bool bCopyCache = (0 == m_runtime_serial_number && ON_Font::FontType::Unset == m_font_type); if ( @@ -5419,144 +6388,25 @@ void ON_Font::Internal_CopyFrom( ON_Font::ON_Font() { - /* - //sz = 192 - //offsets[0] 0 const unsigned __int64 - //this - //offsets[1] 0 const unsigned __int64 - //m_runtime_serial_number - //offsets[2] 4 const unsigned __int64 - //m_windows_logfont_weight - //offsets[3] 8 const unsigned __int64 - //m_point_size - //offsets[4] 16 const unsigned __int64 - //m_apple_font_weight_trait - //offsets[5] 24 const unsigned __int64 - //m_font_weight - //offsets[6] 25 const unsigned __int64 - //offsets[7] 26 const unsigned __int64 - //offsets[8] 27 const unsigned __int64 - //offsets[9] 28 const unsigned __int64 - //offsets[10] 29 const unsigned __int64 - //offsets[11] 30 const unsigned __int64 - //offsets[12] 31 const unsigned __int64 - //m_font_type - //offsets[13] 32 const unsigned __int64 - //m_locale_name - //offsets[14] 40 const unsigned __int64 - //offsets[15] 48 const unsigned __int64 - //offsets[16] 56 const unsigned __int64 - //offsets[17] 64 const unsigned __int64 - //offsets[18] 72 const unsigned __int64 - //m_loc_face_name - //offsets[19] 80 const unsigned __int64 - //offsets[20] 88 const unsigned __int64 - //offsets[21] 96 const unsigned __int64 - //m_en_windows_logfont_name - //offsets[22] 104 const unsigned __int64 - //m_simulated - //offsets[23] 105 const unsigned __int64 - //m_reserved1 - //offsets[24] 106 const unsigned __int64 - //m_panose1 - //offsets[25] 116 const unsigned __int64 - //m_font_characteristics_hash - //offsets[26] 136 const unsigned __int64 - //m_reserved2 - //offsets[27] 144 const unsigned __int64 - //m_reserved3 - //offsets[28] 152 const unsigned __int64 - //m_reserved4 - //offsets[29] 160 const unsigned __int64 - //m_font_glyph_cache - //offsets[30] 176 const unsigned __int64 - //m_free_type_face - //offsets[31] 184 const unsigned __int64 - //m_reserved5 - //offsets[32] 192 const unsigned __int64 - //(this+1) - */ - - ////const size_t sz = sizeof(*this); - - ////const char* p[33] = - ////{ - //// (const char*)this, - //// (const char*)&m_runtime_serial_number, - //// (const char*)&m_windows_logfont_weight, - //// (const char*)&m_point_size, - //// (const char*)&m_apple_font_weight_trait, - //// (const char*)&m_font_weight, - //// (const char*)&m_font_style, - //// (const char*)&m_font_stretch, - //// (const char*)&m_font_bUnderlined, - //// (const char*)&m_font_bStrikethrough, - //// (const char*)&m_logfont_charset, - //// (const char*)&m_font_origin, - //// (const char*)&m_font_type, - //// (const char*)&m_locale_name, - //// (const char*)&m_loc_postscript_name, - //// (const char*)&m_en_postscript_name, - //// (const char*)&m_loc_family_name, - //// (const char*)&m_en_family_name, - //// (const char*)&m_loc_face_name, - //// (const char*)&m_en_face_name, - //// (const char*)&m_loc_windows_logfont_name, - //// (const char*)&m_en_windows_logfont_name, - //// (const char*)&m_simulated, - //// (const char*)&m_reserved1, - //// (const char*)&m_panose1, - //// (const char*)&m_font_characteristics_hash, - //// (const char*)&m_reserved2, - //// (const char*)&m_reserved3, - //// (const char*)&m_reserved4, - //// (const char*)&m_font_glyph_cache, - //// (const char*)&m_free_type_face, - //// (const char*)&m_reserved5, - //// (const char*)(this+1), - ////}; - - ////const size_t offsets[sizeof(p)/sizeof(p[0])] = { - //// 0, - //// (size_t)(p[1] - p[0]), - //// (size_t)(p[2] - p[0]), - //// (size_t)(p[3] - p[0]), - //// (size_t)(p[4] - p[0]), - //// (size_t)(p[5] - p[0]), - //// (size_t)(p[6] - p[0]), - //// (size_t)(p[7] - p[0]), - //// (size_t)(p[8] - p[0]), - //// (size_t)(p[9] - p[0]), - - //// (size_t)(p[10] - p[0]), - //// (size_t)(p[11] - p[0]), - //// (size_t)(p[12] - p[0]), - //// (size_t)(p[13] - p[0]), - //// (size_t)(p[14] - p[0]), - //// (size_t)(p[15] - p[0]), - //// (size_t)(p[16] - p[0]), - //// (size_t)(p[17] - p[0]), - //// (size_t)(p[18] - p[0]), - //// (size_t)(p[19] - p[0]), - - //// (size_t)(p[20] - p[0]), - //// (size_t)(p[21] - p[0]), - //// (size_t)(p[22] - p[0]), - //// (size_t)(p[23] - p[0]), - //// (size_t)(p[24] - p[0]), - //// (size_t)(p[25] - p[0]), - //// (size_t)(p[26] - p[0]), - //// (size_t)(p[27] - p[0]), - //// (size_t)(p[28] - p[0]), - //// (size_t)(p[29] - p[0]), - - //// (size_t)(p[30] - p[0]), - //// (size_t)(p[31] - p[0]), - //// (size_t)(p[32] - p[0]) - ////}; - - ////int breakpointhere = 99; + ////const char* p0 = (const char*)this; + ////const char* p1 = (const char*)(&this->m_outline_figure_type); + ////const char* p2 = (const char*)(&this->m_quartet_member); + ////const char* p3 = (const char*)(&this->m_reserved2); + ////const char* p4 = (const char*)(this + 1); + ////const size_t sz1 = (p1 - p0); + ////const size_t sz2 = (p2 - p0); + ////const size_t sz3 = (p3 - p0); + ////const size_t sz4 = (p4 - p0); + ////if (sz != sz4 || sz1 <= 0 && sz2 <= sz1 && sz3 <= sz4 || sz4 != sz) + //// ON_TextLog::Null.PrintNewLine(); + /* + sz1 144 const unsigned __int64 + sz2 145 const unsigned __int64 + sz3 146 const unsigned __int64 + sz4 192 const unsigned __int64 + sz 192 const unsigned __int64 + */ } ON_Font::ON_Font( @@ -5650,6 +6500,65 @@ bool ON_Font::SetFontCharacteristics( ); } +bool ON_Font::SetFontCharacteristicsForExperts( + double point_size, + const ON_wString postscript_name, + const ON_wString quartet_name, + ON_FontFaceQuartet::Member quartet_member, + const ON_wString family_name, + const ON_wString face_name, + ON_Font::Weight font_weight, + ON_Font::Style font_style, + ON_Font::Stretch font_stretch, + bool bUnderlined, + bool bStrikethrough, + unsigned char logfont_charset, + int windows_logfont_weight, + double apple_font_weight_trait, + ON_PANOSE1 panose1 +) +{ + this->m_point_size = point_size; + + this->m_windows_logfont_weight = windows_logfont_weight; + this->m_apple_font_weight_trait = apple_font_weight_trait; + + this->m_en_windows_logfont_name = quartet_name.Duplicate(); + this->m_en_windows_logfont_name.TrimLeftAndRight(); + this->m_en_windows_logfont_name = this->m_en_windows_logfont_name; + + this->m_quartet_member = quartet_member; + + this->m_en_postscript_name = postscript_name.Duplicate(); + this->m_en_postscript_name.TrimLeftAndRight(); + this->m_loc_postscript_name = this->m_en_postscript_name; + + this->m_en_family_name = family_name.Duplicate(); + this->m_en_family_name.TrimLeftAndRight(); + this->m_loc_family_name = this->m_en_family_name; + + this->m_en_face_name = face_name.Duplicate(); + this->m_en_face_name.TrimLeftAndRight(); + this->m_loc_face_name = this->m_en_face_name; + + this->m_font_weight = font_weight; + this->m_font_stretch = font_stretch; + this->m_font_style = font_style; + + this->m_font_bUnderlined = bUnderlined; + this->m_font_bStrikethrough = bStrikethrough; + + this->m_logfont_charset = logfont_charset; + + this->m_panose1 = panose1; + + this->m_font_origin = ON_Font::Origin::Unset; + this->m_simulated = 0; + this->m_font_characteristics_hash = ON_SHA1_Hash::ZeroDigest; + + return this->IsValid(); +} + bool ON_Font::IsValidFaceName( const wchar_t* face_name ) @@ -6542,7 +7451,7 @@ public: if ( m_fake_logfont_name.EqualOrdinal(family_name, true) || (ON_FontFaceQuartet::Member::Unset != quartet_member && m_fake_logfont_name.IsEmpty()) - || m_family_and_postcript_name_hash.IsZeroDigentOrEmptyContentHash() + || m_family_and_postcript_name_hash.IsZeroDigestOrEmptyContentHash() ) { ON_ERROR("Invalid input."); @@ -6875,7 +7784,7 @@ const ON_wString ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames( if ( fake_name && ON_FontFaceQuartet::Member::Unset != fake_name->QuartetMember() - && false == fake_name->QuartetFamilyAndPostscriptNameHash().IsZeroDigentOrEmptyContentHash() + && false == fake_name->QuartetFamilyAndPostscriptNameHash().IsZeroDigestOrEmptyContentHash() && fake_name->FakeWindowsLogfontName().IsNotEmpty() ) { @@ -6887,6 +7796,146 @@ const ON_wString ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames( return family_name; // use family_name as the fake LOGFONT name } +static bool Internal_TestInstalledFontsFailure() +{ + return false; // <- breakpoint here +} + +bool ON_Font::TestInstalledFontList(ON_TextLog& text_log) +{ + const class ON_FontList& font_list = ON_Font::InstalledFontList(); + const unsigned font_count = font_list.Count(); + if (font_count <= 0) + { + text_log.Print("ERROR: 0 = ON_Font::InstalledFontList().Count()\n"); + return Internal_TestInstalledFontsFailure(); + } + + const ON_SimpleArray< const class ON_Font* >& by_hash = font_list.ByFontCharacteristicsHash(); + if (font_count != by_hash.UnsignedCount()) + { + text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList()..FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(),false)\n"); + return Internal_TestInstalledFontsFailure(); + } + + const bool bReturnFirst = false; + bool bPassedTest = true; + + text_log.Print("Testing %u installed fonts:\n", font_count); + { + const ON_TextLogIndent indent1(text_log); + + + text_log.Print(L"FromFontCharacteristicsHash() tests ..."); + { + ON_TextLogIndent indent2(text_log); + unsigned error_count = 0; + for (unsigned i = 0; i < font_count; ++i) + { + const ON_Font* f = by_hash[i]; + const ON_SHA1_Hash h = f->FontCharacteristicsHash(); + + const ON_Font* f1 = f = font_list.FromFontCharacteristicsHash(h, bReturnFirst); + if (f != f1) + { + if (0 == error_count) + text_log.PrintNewLine(); + ++error_count; + text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList().FromFontCharacteristicsHash(by_hash[%u],false).\n", i); + bPassedTest = false; + } + } + if (error_count > 0) + text_log.Print("FAILED. %u errors.\n", error_count); + else + text_log.Print(" passed.\n"); + } + + { + const ON_Font* f = font_list.FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(), bReturnFirst); + if (nullptr == f) + { + text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList()..FromFontCharacteristicsHash(ON_Font::Default.FontCharacteristicsHash(),false)\n"); + bPassedTest = false; + } + } + } + + const ON_ClassArray< ON_FontFaceQuartet >& quartet_list = font_list.QuartetList(); + const unsigned quartet_count = quartet_list.UnsignedCount(); + text_log.Print("Testing %u quartets:\n", quartet_count); + { + const ON_TextLogIndent indent1(text_log); + + unsigned error_count = 0; + for (unsigned i = 0; i < quartet_count; ++i) + { + const ON_FontFaceQuartet& q = quartet_list[i]; + const ON_wString qname = q.QuartetName(); + if (qname.IsEmpty()) + { + ++error_count; + text_log.Print("ERROR: nullptr = quartet_list[%u].QuartetName() is empty\n", i); + bPassedTest = false; + continue; + } + const ON_FontFaceQuartet q1 = font_list.QuartetFromQuartetName(qname); + bool bQuartetFromQuartetNamePass = q.QuartetName() == q1.QuartetName(); + const ON_Font* f[4] = { q.RegularFace(),q.BoldFace(),q.ItalicFace(),q.BoldItalicFace() }; + const ON_Font* f1[4] = { q1.RegularFace(),q1.BoldFace(),q1.ItalicFace(),q1.BoldItalicFace() }; + const bool bExpectedIsBoldInQuartet[4] = { false,true,false,true }; + const bool bExpectedIsItalicInQuartet[4] = { false,false,true,true }; + const ON_wString qface[4] = { ON_wString(L"regular"), ON_wString(L"bold"), ON_wString(L"italic"), ON_wString(L"bolditalic") }; + for (unsigned k = 0; k < 4; ++k) + { + if (bQuartetFromQuartetNamePass && f[k] != f1[k]) + bQuartetFromQuartetNamePass = false; + if (nullptr != f[k]) + { + ON_wString qname1 = qname; + qname1 += L" ("; + qname1 += qface[k]; + qname1 += L")"; + const ON_SHA1_Hash h = f[k]->FontCharacteristicsHash(); + const ON_Font* f2 = font_list.FromFontCharacteristicsHash(h, bReturnFirst); + if (f2 != f[k]) + { + ++error_count; + text_log.Print("ERROR: nullptr = ON_Font::InstalledFontList().FromFontCharacteristicsHash(%ls,false).\n", qname1.Array()); + bPassedTest = false; + } + const bool bIsBoldInQuartet = f[k]->IsBoldInQuartet(); + const bool bIsItalicInQuartet = f[k]->IsItalicInQuartet(); + if (bIsBoldInQuartet != bExpectedIsBoldInQuartet[k]) + { + ++error_count; + text_log.Print("ERROR: IsBoldInQuartet(%ls) = %ls.\n", qname1.Array(), bIsBoldInQuartet?L"true":L"false"); + bPassedTest = false; + } + if (bIsItalicInQuartet != bExpectedIsItalicInQuartet[k]) + { + ++error_count; + text_log.Print("ERROR: IsItalicInQuartet(%ls) = %ls.\n", qname1.Array(), bIsItalicInQuartet ? L"true" : L"false"); + bPassedTest = false; + } + } + } + if (false == bQuartetFromQuartetNamePass) + { + ++error_count; + text_log.Print(L"ERROR: QuartetFromQuartetName(%ls) failed.\n",static_cast(qname)); + } + } + if (error_count > 0) + text_log.Print("FAILED. %u quartet errors.\n", error_count); + else + text_log.Print("Passed.\n"); + } + + return bPassedTest; +} + + const ON_wString ON_Font::QuartetName( ON_Font::NameLocale name_locale ) const @@ -6914,53 +7963,189 @@ const ON_wString ON_Font::QuartetName( return WindowsLogfontName(name_locale); } +ON_FontFaceQuartet::Member ON_Font::QuartetFaceMember() const +{ + // DO NOT call any other code to guess the anser. + // This value is set only when the quartet member is known with 100% certainty. + // See IsBoldInQuartet() or IsItalicInQuartet() for code that starts + // guessing when m_quartet_member is Unset. + return this->m_quartet_member; +} + const ON_wString ON_Font::QuartetName() const { return ON_Font::QuartetName(ON_Font::NameLocale::LocalizedFirst); } +const ON_wString ON_Font::QuartetDescription() const +{ + ON_wString s = this->QuartetName(); + if (s.IsEmpty()) + return ON_wString::EmptyString; + if (s.IsNotEmpty()) + { + const ON_FontFaceQuartet::Member m = ON_Font::QuartetFaceMember(); + if (ON_FontFaceQuartet::Member::Unset != m) + { + s += L" ("; + s += ON_FontFaceQuartet::MemberToString(m); + s += L")"; + } + } + return s; +} + bool ON_Font::IsBoldInQuartet() const { - for (;;) + if (ON_FontFaceQuartet::Member::Unset != this->m_quartet_member) { - const ON_FontFaceQuartet q = InstalledFontQuartet(); - const bool bQuartetItalic = (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style); - - const ON_Font* regular - = bQuartetItalic - ? q.ItalicFace() - : q.RegularFace(); - - const ON_Font* bold - = bQuartetItalic - ? q.BoldItalicFace() - : q.BoldFace(); - - if (nullptr == regular && nullptr == bold) - break; - - if (nullptr == bold) - return false; // No bold in this quartet. - - if (nullptr == regular) - return true; // no regular in this quartet. - - if (this == bold) - return true; - if (this == regular) - return false; - - const unsigned int font_weight = static_cast(FontWeight()); - const unsigned int regular_weight = static_cast(regular->FontWeight()); - const unsigned int bold_weight = static_cast(bold->FontWeight()); - if (regular_weight < bold_weight) - { - return (2 * font_weight > (regular_weight + bold_weight)); - } - return (font_weight > regular_weight); + return ON_FontFaceQuartet::Member::Bold == this->m_quartet_member || ON_FontFaceQuartet::Member::BoldItalic == this->m_quartet_member; } - return IsBold(); + // ... sigh, start guessing from imperfect information + const ON_Font::Weight font_weight = this->FontWeight(); + + ON_FontFaceQuartet q = FontQuartet(); + + if (q.IsEmpty()) + { + // this font is not associated with a quartet. + // The best we can do is look at the unreliable m_font_weight setting. + // This will be wrong for light, heavy, black, ... fonts. + return ON_Font::IsBoldWeight(font_weight); + } + + const bool bProbablyItalic = (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style); + + const ON_Font* regular2[2] = { q.Face(false,bProbablyItalic), q.Face(false,!bProbablyItalic) }; + const ON_Font* bold2[2] = { q.Face(true,bProbablyItalic), q.Face(true,!bProbablyItalic) }; + + if (this == regular2[0] || this == regular2[1]) + return false; + if (this == bold2[0] || this == bold2[1]) + return false; + + const ON_SHA1_Hash this_hash = this->FontCharacteristicsHash(); + + + if (nullptr != regular2[0] && regular2[0]->FontCharacteristicsHash() == this_hash) + return false; + if (nullptr != regular2[1] && regular2[1]->FontCharacteristicsHash() == this_hash) + return false; + + if (nullptr != bold2[0] && bold2[0]->FontCharacteristicsHash() == this_hash) + return true; + if (nullptr != bold2[1] && bold2[1]->FontCharacteristicsHash() == this_hash) + return true; + + if (this->IsInstalledFont()) + { + // Doesn't work with managed fonts (missing) because their quartets are typically not completely filled in. + // NOTE The q.IsEmpty() test above insures at least one of regular2[] or bold2[] has a non nullptr. + if (nullptr == bold2[0] && nullptr == bold2[1]) + return false; // every face in this quartet is regular. + if (nullptr == regular2[0] && nullptr == regular2[1]) + return true; // every face in this quartet is bold. + } + + if (ON_Font::Weight::Unset == this->FontWeight()) + return false; // who knows? + + if (nullptr == regular2[0]) + regular2[0] = regular2[1]; + if (nullptr == bold2[0]) + bold2[0] = bold2[1]; + + unsigned int regular_weight; + if (nullptr != regular2[0] && ON_Font::Weight::Unset != regular2[0]->FontWeight()) + regular_weight = static_cast(regular2[0]->FontWeight()); + else if (nullptr != bold2[0] && ON_Font::Weight::Unset != bold2[0]->FontWeight()) + regular_weight = static_cast(bold2[0]->FontWeight())-1; + else + regular_weight = static_cast(ON_Font::Weight::Normal); + + return static_cast(font_weight) > regular_weight; +} + + +bool ON_Font::IsItalicInQuartet() const +{ + if (ON_FontFaceQuartet::Member::Unset != this->m_quartet_member) + { + return ON_FontFaceQuartet::Member::Italic == this->m_quartet_member || ON_FontFaceQuartet::Member::BoldItalic == this->m_quartet_member; + } + + const bool bProbablyItalic = (ON_Font::Style::Italic == m_font_style || ON_Font::Style::Oblique == m_font_style); + + const ON_FontFaceQuartet q = InstalledFontQuartet(); // only look at installed quartets for italic query + if (q.IsEmpty()) + return bProbablyItalic; // this font is not in a quartet + + const ON_SHA1_Hash this_hash = this->FontCharacteristicsHash(); + + const ON_Font* upright2[2] = { q.RegularFace(), q.BoldFace() }; + if (nullptr != upright2[0] && upright2[0]->FontCharacteristicsHash() == this_hash) + return false; + if (nullptr != upright2[1] && upright2[1]->FontCharacteristicsHash() == this_hash) + return false; + + + const ON_Font* italic2[2] = { q.ItalicFace(), q.BoldItalicFace() }; + if (nullptr != italic2[0] && italic2[0]->FontCharacteristicsHash() == this_hash) + return true; + if (nullptr != italic2[1] && italic2[1]->FontCharacteristicsHash() == this_hash) + return true; + + // NOTE The q.IsEmpty() test above insures at least one of upright2[] or italic2[] has a non nullptr. + + if (nullptr == italic2[0] && nullptr == italic2[1]) + return false; // every font in htis quartet is upright. + + if (nullptr == upright2[0] && nullptr == upright2[1]) + return true; // every font in this quartet is italic. + + bool bBoldInQuartet = this->IsBoldInQuartet(); + const ON_Font* upright = upright2[bBoldInQuartet ? 1 : 0]; + const ON_Font* italic = italic2[bBoldInQuartet ? 1 : 0]; + if (nullptr == upright && nullptr == italic) + { + // Assume regular/bold is not reliable + upright = upright2[bBoldInQuartet ? 0 : 1]; + italic = italic2[bBoldInQuartet ? 0 : 1]; + } + + if (nullptr == italic) + return false; // No italic with same regular/bold in this quartet. + + if (nullptr == upright) + return true; // No upright with same regular/bold in this quartet. + + return bProbablyItalic; // best we can do +} + +const ON_FontFaceQuartet ON_Font::FontQuartet() const +{ + const ON_wString quartet_name[2] = { QuartetName(), this->RichTextFontName() }; + const ON_SHA1_Hash quartet_name_hash[2] = { ON_Font::FontNameHash(quartet_name[0],false), ON_Font::FontNameHash(quartet_name[1],false) }; + + const int imin = quartet_name[0].IsNotEmpty() ? 0 : 1; + const int imax = (quartet_name[1].IsNotEmpty() && (quartet_name[0].IsEmpty() || quartet_name_hash[0] != quartet_name_hash[1])) ? 1 : 0; + + for (int i = imin; i <= imax; ++i) + { + ON_FontFaceQuartet installed_quartet = ON_Font::InstalledFontList().QuartetFromQuartetName(quartet_name[i]); + if (installed_quartet.IsNotEmpty()) + return installed_quartet; + } + + for (int i = imin; i <= imax; ++i) + { + ON_FontFaceQuartet managed_quartet = ON_Font::ManagedFontList().QuartetFromQuartetName(quartet_name[i]); + if (managed_quartet.IsNotEmpty()) + return managed_quartet; + } + + return ON_FontFaceQuartet::Empty; } const ON_FontFaceQuartet ON_Font::InstalledFontQuartet() const @@ -7495,6 +8680,54 @@ bool ON_Font::IsSingleStrokeOrDoubleStrokeFont() const ); } +const ON_Font* ON_Font::DefaultEngravingFont() +{ + // The PostScript name works on Widows and is the best way on Apple. + /* + Font description = "SLF-RHN Architect Regular" + Family name = "SLF-RHN Architect" + Face name = "Regular" + PostScript name = "SLFRHNArchitect-Regular" + Quartet = SLF-RHN Architect (Regular) + Windows LOGFONT name = "SLF-RHN Architect" + Rich text name = "SLF-RHN Architect" + Origin = Windows Font + Outline type = Engraving - single stroke + PointSize = annotation default + Quartet: SLF-RHN Architect (Regular member) + Weight = Normal + Stretch = Medium + Style = Upright + Underlined = false + Strikethrough = false + Symbol font = false + Engraving font = Single-stroke + Font characteristics SHA-1 hash = E5527949C70756BFBC586AB04A9CFAD9FB5D9038 + LOGFONT + */ + static const ON_Font* default_engraving_font = nullptr; + + if (nullptr == default_engraving_font) + { + default_engraving_font = ON_Font::InstalledFontList().FromNames( + L"SLFRHNArchitect-Regular", // postscript_name, + L"SLF-RHN Architect", // windows_logfont_name, + L"SLF-RHN Architect", // family_name, + L"Regular", // prefered_face_name, + ON_Font::Weight::Normal, // prefered_weight, + ON_Font::Stretch::Medium, // prefered_stretch, + ON_Font::Style::Upright, // prefered_style, + false, // bRequireFaceMatch, + false, // bRequireStyleMatch, + false, // bUnderlined, + false, // bStrikethrough, + 0.0 // point_size + ); + } + + return default_engraving_font; +} + bool ON_Font::IsEngravingFont() const { const ON_wString names[] = @@ -8448,6 +9681,10 @@ void ON_Font::Dump(ON_TextLog& dump) const if ( en_postscript_name.IsNotEmpty() && en_postscript_name != postscript_name) dump.Print(L"PostScript name (English)= \"%ls\"\n", static_cast(en_postscript_name )); + const ON_wString quartet_description = this->QuartetDescription(); + if (quartet_description.IsNotEmpty()) + dump.Print(L"Quartet = %ls\n", static_cast(quartet_description)); + const ON_wString windows_logfont_name = WindowsLogfontName(); dump.Print(L"Windows LOGFONT name = \"%ls\"\n", static_cast(windows_logfont_name)); const ON_wString en_windows_logfont_name = WindowsLogfontName(ON_Font::NameLocale::English); @@ -8457,6 +9694,13 @@ void ON_Font::Dump(ON_TextLog& dump) const const ON_wString rich_text_name = ON_Font::RichTextFontName(this, false); dump.Print(L"Rich text name = \"%ls\"\n", static_cast(rich_text_name)); + if (this->IsManagedSubstitutedFont()) + { + const ON_Font* substitute = this->SubstituteFont(); + if (nullptr != substitute) + dump.Print(L"Installed substitute = %ls\n", static_cast(substitute->Description())); + } + s = ON_wString::EmptyString; switch (this->FontOrigin()) { @@ -8501,7 +9745,7 @@ void ON_Font::Dump(ON_TextLog& dump) const dump.Print(L"PointSize = annotation default\n"); } - const ON_FontFaceQuartet q = this->InstalledFontQuartet(); + const ON_FontFaceQuartet q = this->FontQuartet(); const ON_FontFaceQuartet::Member m = q.QuartetMember(this); switch (m) { @@ -9180,7 +10424,7 @@ bool ON_Font::Write( } - if (!file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,5)) + if (!file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,6)) return false; bool rc = false; @@ -9311,6 +10555,11 @@ bool ON_Font::Write( if (!m_panose1.Write(file)) break; + // version 1.6 rich text quartet member (regular/bold/italic/bold-italic) + const unsigned char quartet_member_as_unsigned = static_cast(this->m_quartet_member); + if (!file.WriteByte(1,&quartet_member_as_unsigned)) + break; + rc = true; break; } @@ -9656,6 +10905,17 @@ bool ON_Font::Read( break; } + // version 1.6 rich text quartet member (regular/bold/italic/bold-italic) + unsigned char quartet_member_as_unsigned = static_cast(this->m_quartet_member); + if (!file.ReadByte(1,&quartet_member_as_unsigned)) + break; + this->m_quartet_member = ON_FontFaceQuartet::MemberFromUnsigned(quartet_member_as_unsigned); + + if (minor_verision <= 6) + { + rc = true; + break; + } rc = true; break; @@ -9917,103 +11177,94 @@ const ON_wString ON_Font::Description( return Description(ON_Font::NameLocale::LocalizedFirst, ON_wString::HyphenMinus, ON_wString::Space, true); } + const ON_wString ON_Font::Description( ON_Font::NameLocale name_local, wchar_t family_separator, wchar_t weight_width_slope_separator, bool bIncludeUndelinedAndStrikethrough ) const +{ + // Do not change bIncludeNotOnDevice to false. + // If you have a situation where you do not want the + // missing font description, then use the override that + // has bIncludeNotOnDevice as a parameter. + const bool bIncludeNotOnDevice = true; + + return this->Description( + name_local, + family_separator, + weight_width_slope_separator, + bIncludeUndelinedAndStrikethrough, + bIncludeNotOnDevice + ); +} + +const ON_wString ON_Font::Description( + ON_Font::NameLocale name_local, + wchar_t family_separator, + wchar_t weight_width_slope_separator, + bool bIncludeUndelinedAndStrikethrough, + bool bIncludeNotOnDevice +) const { ON_wString description; - if ((FamilyName().IsEmpty() || FaceName().IsEmpty()) && WindowsLogfontName().IsNotEmpty()) + + if (bIncludeNotOnDevice && this->IsManagedSubstitutedFont()) { - description = WindowsLogfontName(); + description += ON_wString(L"[Not on device] "); } - else if ( FamilyName().IsNotEmpty() ) + + ON_wString family_name = FamilyName(); + family_name.TrimLeftAndRight(); + ON_wString logfont_name = WindowsLogfontName(); + logfont_name.TrimLeftAndRight(); + if (family_name.IsEmpty() && logfont_name.IsNotEmpty()) { - description = FamilyName(); - if ( FaceName().IsNotEmpty() ) + // LOGFONT (width-weight-slant) + description += logfont_name; + + const ON_wString wws = this->WidthWeightSlantDescription(); + if (wws.IsNotEmpty()) { - description += L" "; - description += FaceName(); + description += ON_wString(L" ("); + description += wws; + description += ON_wString(L")"); + } + } + else if (family_name.IsNotEmpty() ) + { + // family face + description += family_name; + ON_wString face_name = FaceName(); + face_name.TrimLeftAndRight(); + if ( face_name.IsNotEmpty() ) + { + description += ON_wString(L" "); + description += face_name; } } else { - description = ON_Font::FamilyNameFromDirtyName(PostScriptName()); + ON_wString postscript_name = PostScriptName(); + postscript_name.TrimLeftAndRight(); + if (postscript_name.IsNotEmpty()) + description += postscript_name; } - wchar_t separator = family_separator; + if (description.IsEmpty()) + description = this->WidthWeightSlantDescription(); - ON_wString weight; - switch (FontWeight()) + if (description.IsNotEmpty()) { - case ON_Font::Weight::Unset: - break; - case ON_Font::Weight::Normal: - break; - - default: - weight = ON_Font::WeightToWideString(FontWeight()); - break; - } - if (weight.IsNotEmpty()) - { - description += separator; - description += weight; - separator = weight_width_slope_separator; - } - - ON_wString stretch; - switch (FontStretch()) - { - case ON_Font::Stretch::Unset: - break; - case ON_Font::Stretch::Medium: - break; - - default: - stretch = ON_Font::StretchToWideString(FontStretch()); - break; - }; - if (stretch.IsNotEmpty()) - { - description += separator; - description += stretch; - separator = weight_width_slope_separator; - } - - - ON_wString slope; - switch (FontStyle()) - { - case ON_Font::Style::Unset: - break; - case ON_Font::Style::Upright: - break; - default: - slope = ON_Font::StyleToWideString(FontStyle()); - break; - } - if (slope.IsNotEmpty()) - { - description += separator; - description += slope; - separator = weight_width_slope_separator; - } - - if (IsUnderlined()) - { - description += separator; - description += L"Underlined"; - separator = weight_width_slope_separator; - } - - if (IsStrikethrough()) - { - description += separator; - description += L"Strikethrough"; - separator = weight_width_slope_separator; + const bool bUnderlined = IsUnderlined(); + const bool bStrikethrough = IsStrikethrough(); + if (bUnderlined && bStrikethrough) + description += ON_wString(L" (underlined,strikethrough)"); + else if (bUnderlined) + description += ON_wString(L" (underlined)"); + else if (bStrikethrough) + description += ON_wString(L" (strikethrough)"); } return description; @@ -10163,7 +11414,7 @@ unsigned int ON_Font::SetUnsetProperties( if (changed_property_count > 0) { m_simulated = 0; - m_managed_face_is_installed = 0; + m_managed_installed_font_and_bits = 0; Internal_AfterModification(); } @@ -10395,6 +11646,7 @@ void ON_Font::Internal_ClearName( { m_loc_windows_logfont_name = ON_wString::EmptyString; m_en_windows_logfont_name = ON_wString::EmptyString; + m_quartet_member = ON_FontFaceQuartet::Member::Unset; } } @@ -10776,6 +12028,9 @@ const class ON_SHA1_Hash& ON_Font::FontCharacteristicsHash() const sha1.AccumulateUnsigned64(sizeof(*this)); + // Even on Apple OS, we need to accumulate the WindowsLogfontName() + // because it is what is used for the fake regular/bold/italic/bold-italic + // user interface the Rhino uses on Apple. ON_SHA1_Hash string_hash; const ON_wString windows_logfont_name = WindowsLogfontName(); if ( windows_logfont_name.IsNotEmpty() ) @@ -10807,6 +12062,12 @@ const class ON_SHA1_Hash& ON_Font::FontCharacteristicsHash() const #endif #if defined(ON_RUNTIME_APPLE) + + // The PostScript name is not hashed on Windows because + // it is not predictable with simulated fonts. + // The PostScript name is critical on Apple OS and + // is the most reliable way to uniquely identify + // a font on Apple OS. sha1.AccumulateDouble(m_apple_font_weight_trait); const ON_wString postscript_name = PostScriptName(); if (postscript_name.IsNotEmpty()) @@ -12510,11 +13771,68 @@ const ON_FontList& ON_ManagedFonts::InstalledFonts() #endif if (device_list.Count() > 0) { + // Cull exact duplicates which occur in fonts like SLF..., Noto Emoji", ... + const int count = device_list.Count(); + ON_SimpleArray index(count); + for (int i = 0; i < count; ++i) + index.Append(i); + ON_Sort(ON::sort_algorithm::quick_sort, index.Array(), device_list.Array(), device_list.Count(), sizeof(ON__UINT_PTR), (int (*)(const void*, const void*))ON_FontList::CompareFontCharacteristicsHash); + int j0 = index[0]; + bool bCullNulls = false; + for (int i = 1; i < count; ++i) + { + int j1 = index[i]; + if (j0 == j1) + continue; + const ON_Font* f0 = device_list[j0]; + const ON_Font* f1 = device_list[j1]; + if (0 == ON_FontList::CompareFontCharacteristicsHash(&f0, &f1)) + { + // duplicate font - keep the first one the platform delivered. + if (j0 < j1) + { + device_list[j1] = nullptr; + delete const_cast(f1); + bCullNulls = true; + } + else if (j0 > j1) + { + device_list[j0] = nullptr; + delete const_cast(f0); + j0 = j1; + bCullNulls = true; + } + } + else + j0 = j1; + } + + if (bCullNulls) + { + int count1 = 0; + for (int i = 0; i < count; ++i) + { + const ON_Font* f = device_list[i]; + if (nullptr == f) + continue; + device_list[count1++] = f; + } + device_list.SetCount(count1); + } + List.m_installed_fonts.AddFonts(device_list); List.m_installed_fonts.Internal_UpdateSortedLists(); } } + + if (List.m_installed_fonts.Count() > 0) + { + // Calling List.m_installed_fonts.QuartetList() sets ON_Font::m_quartet_member + // for all installled font on Apple and for damaged installed fonts on Windows. + // See comments near the end of ON_FontList::QuartetList(). + List.m_installed_fonts.QuartetList(); + } + return List.m_installed_fonts; } - diff --git a/opennurbs_font.h b/opennurbs_font.h index 8b90a2ba..2c320774 100644 --- a/opennurbs_font.h +++ b/opennurbs_font.h @@ -371,6 +371,7 @@ public: */ int AscentOfx() const; + /* Description: Parameters: @@ -449,7 +450,7 @@ public: void SetAscentOfx( int ascent_of_x ); - + void SetStrikeout( int strikeout_position, int strikeout_thickness @@ -474,7 +475,7 @@ public: void SetAscentOfx( double ascent_of_x ); - + void SetStrikeout( double strikeout_position, double strikeout_thickness @@ -536,7 +537,7 @@ private: int m_descent = 0; // min over all glyphs in font of (lowest outline point - baseline point).y int m_line_space = 0; // distance between baselines ON__UINT16 m_ascent_of_capital = 0; - ON__UINT16 m_ascent_of_x = 0; + ON__UINT16 m_ascent_of_x = 0; // same units as m_ascent_of_capital int m_strikeout_thickness = 0; // int m_strikeout_position = 0; // @@ -983,6 +984,50 @@ public: */ double BoxArea() const; + /* + Description: + Determines if this ON_OutlineFigure is inside of outer_figure. + Parameters: + outer_figure - [in] + When bPerformExtraChecking is false, outer_figure->FigureOrientation() should + be set to what you plan on using when rendering the glyph. + The orientation of outer_figur can be either clockwise or counterclockwise + and, in the context of the entire glyph, outer_figure can be an inner or outer boundary. + For example, the registered trademark glpyh (UNICODE U+00AE) is an example where + four nested figures with alternating orientations are common. + bPerformExtraChecking - [in] + In general, when sorting glyph outlines as they come froma font file, set + outer_figure->FigureOrientation() to what will be used to render the glyph + and pass false for bPerformExtraChecking. + Details: In the case when bounding boxes and estimated areas and spot checks of winding numbers + all indicate that this is inside of other_f, an additional time consuming intersection + check is performed when this->FigureOrientation() == other_f->FigureOrientation(). + When this->FigureOrientation() and other_f->FigureOrientation() are opposited, + the additional intersection check is skipped unless bPerformExtraChecking is true. + Returns: + True if it is very likely that this is not empty and is inside of other_f. + False otherwise + Remarks: + */ + bool IsInsideOf( + const ON_OutlineFigure* outer_figure, + bool bPerformExtraChecking + ) const; + + /* + Description: + Get up to four distinct points on the figure. + These are useful for winding number tests when sorting figures. + Parameters: + p - [out] + the returned points will be on the figure (not bezier interior control points). + Returns: + Number of points. + */ + unsigned GetUpToFourPointsOnFigure( + ON_2fPoint p[4] + ) const; + ON__UINT32 UnitsPerEM() const; ON__UINT16 FigureIndex() const; @@ -2245,6 +2290,24 @@ public: unsigned int member_as_unsigned ); + static ON_FontFaceQuartet::Member MemberFromBoldAndItalic( + bool bMemberIsBold, + bool bMemberIsItalic + ); + + /* + Description: + When an exact quartet face bold/italic match is not available, choosing + an available quartet face that minimizes ON_FontFaceQuartet::BoldItalicDeviation() + is one way to select which available quartet face to use. + Returns: + A distance between two quartet face members. + */ + static unsigned BoldItalicDeviation( + ON_FontFaceQuartet::Member desired_member, + ON_FontFaceQuartet::Member available_member + ); + ON_FontFaceQuartet() = default; ~ON_FontFaceQuartet() = default; ON_FontFaceQuartet(const ON_FontFaceQuartet&) = default; @@ -2279,14 +2342,23 @@ public: bool HasItalicFace() const; bool HasBoldItalicFace() const; bool HasAllFaces() const; + + + /// True if FaceCount() = 0. (The name may be empty or not empty.) bool IsEmpty() const; - /* - Returns: - 0,1,2,3 or 4 - */ + /// True if FaceCount() > 0. (The name may be empty or not empty.) + bool IsNotEmpty() const; + + /// Total number of available faces (0 to 4). unsigned int FaceCount() const; + /// Number of faces that are not installed on this device (0 to FaceCount()). + unsigned int NotInstalledFaceCount() const; + + /// Number of faces that are simulated (0 to FaceCount()). + unsigned int SimulatedFaceCount() const; + const ON_wString QuartetName() const; const class ON_Font* RegularFace() const; const class ON_Font* BoldFace() const; @@ -2336,6 +2408,7 @@ public: ) const; void Dump(ON_TextLog& text_log) const; + private: ON_wString m_quartet_name; const class ON_Font* m_regular = nullptr; @@ -2481,7 +2554,7 @@ public: */ static ON_Font::Weight FontWeightFromUnsigned( unsigned int unsigned_font_weight - ); + ); /* Description: @@ -2713,6 +2786,18 @@ public: ON_Font::Style font_style ); + + /* + Returns: + If this font is installed or managed, the installed or mangaged font face quartet is returned. + Otherwise ON_FontFaceQuartet::Empty is returned. + Note that managed font quartets can be enlarged to include missing faces by calling + ON_Font::FontFromRichTextProperties(). Installed font quartets exactly match + what is installed on the current defice. + if this font is not a member of an installed face quartet. + */ + const ON_FontFaceQuartet FontQuartet() const; + /* Returns: The installed font face quartet for this font or ON_FontFaceQuartet::Empty @@ -3026,6 +3111,8 @@ public: const ON_Font* ManagedFont() const; /* + Description: + It is better to call ON_Font::FontFromRichTextProperties(). Parameters: rtf_font_name - [in] Rich text format name. This name is not well defined and depends on @@ -3038,8 +3125,9 @@ public: bRtfItalic - [in] RTF italic flag Returns: - A managed font to use for this RTF font. + A managed font to use for these rich text properties. */ + ON_DEPRECATED_MSG("Call ON_Font::FontFromRichTextProperties()") static const ON_Font* ManagedFontFromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, @@ -3137,15 +3225,15 @@ public: /* Parameters: bBold - [in] - True for a heavy face + True for the rich text quartet "bold face" with is typically heavier than the "regular" face. bItalic - [in] - True for a sloped face + True for the rich text quartet "italic" with is typically more slanted than the "regular" face. bUnderlined - [in] True for an underlined face bStrikethrough - [in] True for a strikethrough face Returns: - A managed face in the same family as "this" with the desired rich text properties. + ON_Font::ManagedFontFromRichTextProperties(this->RichTextName(),bBold,bItalic,bUnderlined,bStrikethrough); */ const ON_Font* ManagedFamilyMemberWithRichTextProperties( bool bBold, @@ -3159,23 +3247,96 @@ public: The list of installed fonts in a class with lots of searching tools. */ static const class ON_FontList& InstalledFontList(); - /* + Description: + Tests InstalledFontList(). + Parameters: + text_log - [in] + Summary of the test. + If errors are detected, they are printed in error_log. + Returns: + true: Test passed - no errors detected. + false: Test failed. + */ + static bool TestInstalledFontList( + class ON_TextLog& text_log + ); + + /* + Description: + This is the best way to get a font from rich text properties. + Parameters: + rich_text_font_name - [in] + Rich text quartet name. If you have an ON_Font, then ON_Font.RichTextName() + gets a good choice for this name. + * For Windows installed fonts, this is identical to the Windows LOGFONT.lfFaceName. + * For MacOS this is an invented name and is chosen to work cross platform as well + as possible. For Apple families with up to 4 faces that align with the rich text + quartet "regular/bold/italic/bold-italic" faces, things tend to work as expected. + for common Apple families like Helvetica Neue with a dozen or so faces that are + designed to work well for western european languages, opennurbs selects 4 faces in + the family that tend to align with would many people expect as the rich text + "regular/bold/italic/bold-italic" face. Things get dicier with less common + families and fonts designed for non-western european languages. + Basically, Apple and richt text do not play nicely together. + + bBoldQuartetMember - [in] + True to select the heavier members of the rich text quartet. + + bItalicQuartetMember - [in] + True to select the more slanted memmers of the rich text quartet. + + bUnderlined - [in] + True if you want underlined text. + (Underlining is created as a rendering effect and not a separate face.) + + bStrikethrough - [in] + True if you want strikethrough text. + (Strikethrough is created as a rendering effect and not a separate face.) + + Returns: + If there is an installed font, it is returned. Othewise a managed font + is returned. When the managed font is not installed, the corresponding + member of ON_Font::Default::InstalledQuartet() is used to render the font. + */ + static const ON_Font* FontFromRichTextProperties( + ON_wString rich_text_font_name, + bool bBoldQuartetMember, + bool bItalicQuartetMember, + bool bUnderlined, + bool bStrikethrough + ); + + +private: + // this must be a managed or installed font. + const ON_Font* Internal_DecoratedFont( + bool bUnderlined, + bool bStrikethrough + ) const; + +public: + + /* + Description: + Unless you are certain you want to restrict your choices to installed fonts, + it is better to call ON_Font::FontFromRichTextProperties(). Parameters: rtf_font_name - [in] - Rich text format name. This name is not well defined and depends on - the device and application that created the rich text. On Windows this - is often a LOGFONT.lfFaceName. On MacOS it is often a PostScript name. + Rich text quartet neame. This name is not well defined and depends on + the device and application that created the rich text. For Windows installed + fonts, this is identical to the Windows LOGFONT.lfFaceName. + On MacOS this is a name Rhino cooks up and is chosen to + work cross platform as well as possible. Apple and richt text do not + play nicely together. bRtfBold - [in] - RTF bold flag - + True to prefer the heavier memmbers of the installed rich text quartet. bRtfItalic - [in] - RTF italic flag + True to prefer the more slanted memmbers of the installed rich text quartet. Returns: - An installed font to use for this RTF font or nullptr if the current - device does not have a font with any family, PostScript, or Windows LOGFONT - that has a name matching rtf_font_name. + An installed font to use for this rich text face font or nullptr if the current + device does not have a in installed font with for this rich text quartet. */ static const ON_Font* InstalledFontFromRichTextProperties( const wchar_t* rtf_font_name, @@ -3413,6 +3574,35 @@ public: ON_Font::Default is managed. */ bool IsManagedFont() const; + + /* + Returns: + True if this is a managed font and the font is installed on this device. + False otherwise. + Remarks: + If this->IsManagedFont() is true, then exactly one of IsManagedInstalledFont() + or IsManagedSubstitutedFont() is true. + When this->IsManagedInstalledFont() is true, this->InstalledFont() returns the installed font. + */ + bool IsManagedInstalledFont() const; + + /* + Returns: + True if this font is a managed font that references a font that is not installed on this computer. + Remarks: + If this->IsManagedFont() is true, then exactly one of IsManagedInstalledFont() + or IsManagedSubstitutedFont() is true. + When this->IsManagedSubstitutedFont() is true, this->SubstituteFont() returns the installed font. + */ + bool IsManagedSubstitutedFont() const; + + /* + Returns: + If this font is a managed font that references a font that is not installed on this computer, + then a pointer to the installed font that is the substitue for the missing font is returned. + Otherwise nullptr is returned. + */ + const ON_Font* SubstituteFont() const; /* Returns: @@ -3423,6 +3613,9 @@ public: */ bool IsInstalledFont() const; +private: + const ON_Font* Internal_ManagedFontToInstalledFont() const; + public: ON_Font(); ~ON_Font() = default; @@ -3520,7 +3713,7 @@ public: unsigned int logfont_charset ); - bool SetFontCharacteristics( + bool SetFontCharacteristics( double point_size, const wchar_t* gdi_logfont_name, ON_Font::Weight font_weight, @@ -3530,7 +3723,25 @@ public: bool bStrikethrough, double linefeed_ratio, unsigned int logfont_charset - ); + ); + + bool SetFontCharacteristicsForExperts( + double point_size, + const ON_wString postscript_name, + const ON_wString quartet_name, + ON_FontFaceQuartet::Member quartet_member, + const ON_wString family_name, + const ON_wString face_name, + ON_Font::Weight font_weight, + ON_Font::Style font_style, + ON_Font::Stretch font_stretch, + bool bUnderlined, + bool bStrikethrough, + unsigned char logfont_charset, + int windows_logfont_weight, + double apple_font_weight_trait, + ON_PANOSE1 panose1 + ); /* @@ -3753,6 +3964,27 @@ public: */ const ON_wString QuartetName() const; + /* + Returns: + If known, this font's quartet face (regular, bold, italic, bold-italic). + Otherwise ON_FontFaceQuartet::Member::Unset + Remarks: + For Windows installed fonts, the LOGFONT partition determines the quartet face. + On Apple platforms, opennurbs uses a table for common fonts and leaves the rest unset. + When unset, this is the best way to determine which quartet member this font represents. + In all cases, the absolute weight or the ON_Font.IsBold() is unreliable and should + be avoided at all costs. + */ + ON_FontFaceQuartet::Member QuartetFaceMember() const; + + /* + Description: + Get a string like "Arial (Regular)" that describes this font's quartet. + Returns: + quartet name + (face) + */ + const ON_wString QuartetDescription() const; + /* Returns: A long description that includes family, face, weight, stretch and style information. @@ -3763,7 +3995,12 @@ public: */ const ON_wString Description() const; - /* + const ON_wString WidthWeightSlantDescription() const; + + static const ON_wString WidthWeightSlantDescription(ON_Font::Stretch width, ON_Font::Weight weight, ON_Font::Style slant); + + + /* Description: Get a text descripton with family weight, width (stretch), slope (style). Parameters: @@ -3796,6 +4033,44 @@ public: ) const; + + /* + Description: + Get a text descripton with family weight, width (stretch), slope (style). + Parameters: + family_separator - [in] + character to place after family name in the description. + 0 = no separator. + 0, ON_wSting::HyphenMinus, and ON_wString::Space are common choices. + weight_width_slope_separator - [in] + character to place bewtween weight, stretch, and style descriptions + 0 = no separator. + 0, ON_wSting::HyphenMinus, and ON_wString::Space are common choices. + bIncludeUndelinedAndStrikethrough - [in] + If true, underlined and strikethrough attributes are appended + 0 = no separator. + 0, ON_wSting::HyphenMinus, and ON_wString::Space are common choices. + bIncludeNotOnDevice - [in] + If true and this->IsManagedSubstitutedFont() is true, then the + returned string begins with "[Not on device]" followed by the font's + description. + Returns: + A font description with family name, weight, stretch, and style. + If present, the weight, stretch, style, underlined, and strikethrough + descriptions begin with a capital letter followed by lowercase letters. + Remarks: + A description similar to the PostScript name is returned by + DescriptionFamilyWeightStretchStyle(ON_wString::HyphenMinus,0,false). + However, this often differs from the actual PostScript name. + */ + const ON_wString Description( + ON_Font::NameLocale name_local, + wchar_t family_separator, + wchar_t weight_width_slope_separator, + bool bIncludeUndelinedAndStrikethrough, + bool bIncludeNotOnDevice + ) const; + private: static const ON_wString& Internal_GetName( ON_Font::NameLocale name_locale, @@ -4596,6 +4871,9 @@ public: /* Returns: ON_Font::RichTextFontName(this,false); + Remarks: + For Windows installed fonts, this is identical to the Windows LOGFONT name. + For Apple platforms and rich text quartet names do not play nicely together. */ const ON_wString RichTextFontName() const; @@ -4663,6 +4941,16 @@ public: void Dump( ON_TextLog& ) const; // for debugging + +#if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + // Used to create installed fonts from Apple CTFont + ON_Font( + ON_Font::FontType font_type, + const class ON_AppleCTFontInformation& apple_font_information + ); +#endif + + #if defined(ON_OS_WINDOWS_GDI) public: static void DumpLogfont( @@ -5236,14 +5524,16 @@ private: void Internal_AfterModification(); public: + /* Description: - User interfaces that want to behave as if there are 3 font weights, - light < normal < < bold, can use the functions - ON_Font.IsLight(), - ON_Font.IsNormalWeight(), - ON_Font.IsBold(), - to query font weight ranges. + User interfaces that want to provide a name + regular/bold/italic/bold-italic + font finder must use IsBoldInQuartet() and IsItalicInQuartet(). + + This function looks at weight the font designer assigned to the font. + This is an unreliable way to determine if a font is "light/regular/bold" + compared to other faces in its font family. + Returns: True if FontWeight() is lighter than ON_Font::Weight::Normal */ @@ -5251,12 +5541,13 @@ public: /* Description: - User interfaces that want to behave as if there are 3 font weights, - light < normal < < bold, can use the functions - ON_Font.IsLight(), - ON_Font.IsNormalWeight(), - ON_Font.IsBold(), - to query font weight ranges. + User interfaces that want to provide a name + regular/bold/italic/bold-italic + font finder must use IsBoldInQuartet() and IsItalicInQuartet(). + + This function looks at weight the font designer assigned to the font. + This is an unreliable way to determine if a font is "light/regular/bold" + compared to other faces in its font family. + Returns: True if FontWeight() is ON_Font::Normal or ON_Font::Weight::Medium */ @@ -5264,14 +5555,20 @@ public: /* Description: - User interfaces that want to behave as if there are 3 font weights, - light < normal < < bold, can use the functions - ON_Font.IsLight(), - ON_Font.IsNormalWeight(), - ON_Font.IsBold(), - to query font weight ranges. + User interfaces that want to provide a name + regular/bold/italic/bold-italic + font finder must use IsBoldInQuartet() and IsItalicInQuartet(). + + This function looks at weight the font designer assigned to the font. + This is an unreliable way to determine if a font is "light/regular/bold" + compared to other faces in its font family. + Returns: True if heavier than ON_Font::Weight::Medium. + + Remarks: + Just in case you didn't read the description, ON_Font.IsBold() is a terrible + way to decide if a font is "bold" in a quartet (regular,bold,italic,bold-italic). + Use ON_Font.QuartetFaceMember() */ bool IsBold() const; @@ -5279,18 +5576,36 @@ public: Returns: True if this font is considered a bold member in its installed font ON_FontFaceQuartet. Remarks: - In a traditional regular/bold/italic/bold-italic font face interfaces, + In a traditional regular/bold/italic/bold-italic font face interfaces, "bold" is relative to the quartet members and cannot be determined by inspecting the numerical value of the font's weight. For example, - Arial Black has a weight of 900=ON_FontWeight::Weight::Heavy, + Arial Black has a weight of 900=ON_FontWeight::Weight::Heavy, but the Arial Black quartet has only two faces, regular and italic. - In quartets for fonts with a simulated bold, like AvenirLT-Roman, + In quartets for fonts with a simulated bold, like AvenirLT-Roman, the bold member often has a LOGFONT weight of 551 < SemiBold = 600. The Windows AvenirLT-Roman quartet has four faces and the bold faces in the quartet have weights 551. */ bool IsBoldInQuartet() const; - + + /* + Returns: + True if this font is considered an italic member in its installed font ON_FontFaceQuartet. + Remarks: + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. + */ + bool IsItalicInQuartet() const; + + /* + Remarks: + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. + */ ON_Font::Style FontStyle() const; /* @@ -5308,14 +5623,17 @@ public: ); /* + Description: + If is better to use IsItalicInQuartet(). + Returns: true if FontStyle() is ON_Font::Style::Italic. false if FontStyle() is ON_Font::Style::Upright or .ON_Font::Style::Oblique. Remarks: - The face is sloped so the top is to the right of the base. - NOTE WELL: - When the term "oblique" appears in a face names or descriptions, - it generally means the face is an italic face. + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. */ bool IsItalic() const; @@ -5323,9 +5641,11 @@ public: Returns: true if FontStyle() is ON_Font::Style::Upright. false if FontStyle() is ON_Font::Style::Italic or .ON_Font::Style::Oblique. - Remark: - In face names and descriptions, the terms "Roman", "Normal", or "Regular" - are often used. + Remarks: + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. */ bool IsUpright() const; @@ -5334,10 +5654,10 @@ public: true if FontStyle() is ON_Font::Style::Italic or is ON_Font::Style::Oblique. Otherwise false. Remarks: - The face is sloped so the top is to the left of the base. This is extremely rare. - NOTE WELL: - When the term "oblique" appears in a face names or descriptions, - it generally means the face is an italic face. + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. */ bool IsItalicOrOblique() const; @@ -5346,10 +5666,10 @@ public: true if FontStyle() is ON_Font::Style::Oblique. false if FontStyle() is ON_Font::Style::Upright or .ON_Font::Style::Italic. Remarks: - The face is sloped so the top is to the left of the base. This is extremely rare. - NOTE WELL: - When the term "oblique" appears in a face names or descriptions, - it generally means the face is an italic face. + When working with rich text you want to use IsItalicInQuartet(). + For fonts with a slanted regular face like Corsiva, + ON_Font.FontStyle() = ON_Font::Style::Italic, ON_Font.IsItalic() = true, + and ON_Font.IsItalicInQuartet() = false. */ bool IsOblique(); // ERROR - missing const @@ -5451,6 +5771,8 @@ public: */ bool IsEngravingFont() const; + static const ON_Font* DefaultEngravingFont(); + unsigned char LogfontCharSet() const; bool SetLogfontCharSet( @@ -5719,7 +6041,7 @@ private: private: // = 1 if this is a managed font and the face is installed on the current device. - mutable ON__UINT8 m_managed_face_is_installed = 0; + ON__UINT8 m_reserved1 = 0; private: ON_PANOSE1 m_panose1; @@ -5737,7 +6059,15 @@ private: ON_OutlineFigure::Type m_outline_figure_type = ON_OutlineFigure::Type::Unset; private: - ON__UINT8 m_reserved1 = 0; + // When then is unset, it is the best way to determine what face this font + // corresponds to in its quartet of faces. (regular,bold,italic,bold-italic) + // The Windows OS LOGFONT partitions specify this. On Apple we have a table + // for common fonts and we make it up on the fly for the rest. + // This field is not included in the font hash because it is mutable + // and may get changed as the application adds more managed fonts. + mutable ON_FontFaceQuartet::Member m_quartet_member = ON_FontFaceQuartet::Member::Unset; + +private: ON__UINT16 m_reserved2 = 0; ON__UINT32 m_reserved3 = 0; double m_reserved4 = 0.0; @@ -5776,11 +6106,38 @@ private: ////////////////////////////////////////////////////////////////////////////////// private: - // Only managed fonts have a non-null m_free_type_face face. + // LEGACY field. Windows opennurbs never uses freetype. + // In rare cases, Apple opennurbs used freetype. mutable class ON_FreeTypeFace* m_free_type_face = nullptr; private: - ON__UINT_PTR m_reserved5 = 0; + // If this font is a managed font, then m_managed_installed_font_and_bits encodes + // 1. The installed font used to render this font + // 2. If the installed font is a substituted for font not installed on this device. + // If this font is not a managed font, then m_managed_installed_font_and_bits = 0. + // LEGACY mutable ON__UINT8 m_managed_face_is_installed = 0; 1 = managed and installed 2 = managed and substituted + mutable ON__UINT_PTR m_managed_installed_font_and_bits = 0; + static void Internal_SetManagedFontInstalledFont( + const ON_Font* managed_font, + const ON_Font* installed_font, + bool bInstalledFontIsASubstitute + ); + + /// + /// + /// + /// + /// True if this is a managed font that is installed on this device. False otherwise. + /// + bool Internal_ManagedFontIsInstalled() const; + + /// + /// + /// + /// + /// True if this is a managed font that is not installed on this device. False otherwise. + /// + bool Internal_ManagedFontIsNotInstalled() const; public: /* @@ -5846,7 +6203,7 @@ ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; class ON_CLASS ON_FontList { public: - ON_FontList() = default; + ON_FontList(); /* Parameters: @@ -5858,9 +6215,11 @@ public: bool bMatchUnderlineStrikethroughAndPointSize ); - ~ON_FontList() = default; - ON_FontList(const ON_FontList&) = default; - ON_FontList& operator=(const ON_FontList&) = default; + ~ON_FontList(); + +private: + ON_FontList(const ON_FontList&) = delete; + ON_FontList& operator=(const ON_FontList&) = delete; public: /* @@ -5871,6 +6230,23 @@ public: ON_Font::NameLocale NameLocale() const; + /* + Parameters: + font_characteristics_hash - [in] + bReturnFirst - [in] + If there are multiple fonts with the same hash and bReturnFirst is true, + then the first font with tht hash is returned. + If there are multiple fonts with the same hash and bReturnFirst is false, + then nullptr is returned. + new style or unset if font style is adequate + Returns: + A font with the specified font characteristics hash. + */ + const ON_Font* FromFontCharacteristicsHash( + ON_SHA1_Hash font_characteristics_hash, + bool bReturnFirst + ) const; + const ON_Font* FromPostScriptName( const wchar_t* postscript_name ) const; @@ -5947,6 +6323,7 @@ public: bRtfItalic - [in] RTF italic flag */ + ON_DEPRECATED_MSG("Use the static ON_Font::FontFromRichTextProperties()") const ON_Font* FromRichTextProperties( const wchar_t* rtf_font_name, bool bRtfBold, @@ -6055,7 +6432,7 @@ public: ) const; /* - Properties: + Parameters: family name - [in] desired_weight - [in] desired_stretch - [in] @@ -6072,7 +6449,7 @@ public: ) const; /* - Properties: + Parameters: font - [in] Used to identify the family, desired_weight - [in] @@ -6134,6 +6511,12 @@ public: */ const ON_SimpleArray< const class ON_Font* >& ByQuartetName() const; + /* + Returns: + Array of fonts sorted by ON_Font.yFontCharacteristicsHash(). + */ + const ON_SimpleArray< const class ON_Font* >& ByFontCharacteristicsHash() const; + /* Returns: Array of font face quartets for this list sorted quartet name. @@ -6170,6 +6553,8 @@ public: const wchar_t* quartet_name ) const; + static int CompareFontCharacteristicsHash(ON_Font const* const* lhs, ON_Font const* const* rhs); + static int ComparePostScriptName(ON_Font const* const* lhs, ON_Font const* const* rhs); static int CompareFamilyName(ON_Font const* const* lhs, ON_Font const* const* rhs); static int CompareFamilyAndFaceName(ON_Font const* const* lhs, ON_Font const* const* rhs); @@ -6201,8 +6586,6 @@ public: const ON_Font * const * font_list ); - - private: friend class ON_ManagedFonts; @@ -6243,35 +6626,15 @@ private: // List of recently added fonts unsorted mutable ON_SimpleArray< const ON_Font* > m_unsorted; - // List of fonts sorted by PostScript name - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_postscript_name; + // A single instance is allocated in the default constructor + // and freed in the destructor. You may assume this point is valid. + // ON_FontListImpl contains the sorted lists. + class ON_FontListImpl& m_sorted; - // List of fonts sorted by Windows LOGFONT.lfFaceName name - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_windows_logfont_name; - - // List of fonts sorted by Family name, then FaceName - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_family_name; - - - // List of fonts sorted by English PostScript name - // Only fonts with m_en_postscript_name != m_loc_postscript_name are included here - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_english_postscript_name; - - // List of fonts sorted by English Windows LOGFONT.lfFaceName name - // Only fonts with m_en_windows_logfont_name != m_loc_windows_logfont_name are included here - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_english_windows_logfont_name; - - // List of fonts sorted by English Family name, then English FaceName - // Only fonts with m_en_family_name != m_loc_family_name are included here - // (Recently added fonts may be in m_unsorted[]) - mutable ON_SimpleArray< const ON_Font* > m_by_english_family_name; - - mutable ON_SimpleArray< const ON_Font* > m_by_quartet_name; + // this reserved block is here to keep sizeof(ON_FontList) unchanged between + // Rhino 7.3 and Rhino 7.4 and to insure that any 3rd party code that used ON_FontList + // in Rhino 7.3 will continue to work as expected in Rhino 7.4. + const ON__UINT_PTR m_reserved[20] = {}; // List of quartets sorted by quartet name. mutable ON_ClassArray< ON_FontFaceQuartet > m_quartet_list; diff --git a/opennurbs_fsp.cpp b/opennurbs_fsp.cpp index 30650dc4..8296b9ee 100644 --- a/opennurbs_fsp.cpp +++ b/opennurbs_fsp.cpp @@ -69,6 +69,70 @@ size_t ON_FixedSizePool::SizeofElement() const return m_sizeof_element; } +size_t ON_FixedSizePool::SizeOfPool() const +{ + size_t element_count = 0; + void* next = m_first_block; + for (void* blk = next; nullptr != blk; blk = next) + { + next = *((void**)blk); + element_count += BlockElementCapacity(blk); + } + return element_count * m_sizeof_element; +} + +size_t ON_FixedSizePool::SizeOfUnusedElements() const +{ + return SizeOfPool() - SizeOfActiveElements(); +} + +size_t ON_FixedSizePool::SizeOfActiveElements() const +{ + return m_sizeof_element * ((size_t)m_active_element_count); +} + + +size_t ON_FixedSizePool::DefaultElementCapacityFromSizeOfElement(size_t sizeof_element) +{ + size_t block_element_capacity = 0; + if (sizeof_element <= 0) + { + ON_ERROR("sizeof_element must be > 0"); + return 0; + } + + size_t page_size = ON_MemoryPageSize(); + if (page_size < 512) + page_size = 512; + + // The "overhead" is for the 2*sizeof(void*) ON_FixedSizePool uses at + // the start of each block + 32 bytes extra for the heap manager + // to keep the total allocation not exceeding multiple of page_size. + const size_t overhead = 2 * sizeof(void*) + 32; + + size_t page_count = 1; + block_element_capacity = (page_count * page_size - overhead) / sizeof_element; + while (block_element_capacity < 1000) + { + page_count *= 2; + block_element_capacity = (page_count * page_size - overhead) / sizeof_element; + if (page_count > 8 && block_element_capacity > 64) + { + // for pools with large elements + break; + } + } + + return block_element_capacity; +} + +bool ON_FixedSizePool::Create( + size_t sizeof_element +) +{ + return ON_FixedSizePool::CreateForExperts(sizeof_element, 0, 0); +} + bool ON_FixedSizePool::Create( size_t sizeof_element, size_t element_count_estimate, @@ -92,53 +156,133 @@ bool ON_FixedSizePool::Create( m_sizeof_element = sizeof_element; if ( block_element_capacity <= 0 ) - { - size_t page_size = ON_MemoryPageSize(); - if ( page_size < 512 ) - page_size = 512; - - // The "overhead" is for the 2*sizeof(void*) ON_FixedSizePool uses at - // the start of each block + 32 bytes extra for the heap manager - // to keep the total allocation not exceeding multiple of page_size. - const size_t overhead = 2*sizeof(void*) + 32; - - size_t page_count = 1; - block_element_capacity = (page_count*page_size - overhead)/m_sizeof_element; - while ( block_element_capacity < 1000 ) - { - page_count *= 2; - block_element_capacity = (page_count*page_size - overhead)/m_sizeof_element; - if (page_count > 8 && block_element_capacity > 64) - { - // for pools with large elements - break; - } - } - } + block_element_capacity = ON_FixedSizePool::DefaultElementCapacityFromSizeOfElement(m_sizeof_element); // capacity for the the 2nd and subsequent blocks m_block_element_count = block_element_capacity; // Set m_al_count = capacity of the first block. - // If the estimated number of elements is not too big, - // then make the first block that size. + // If the estimated number of elements is not too big, then make the 1st block that size. if ( element_count_estimate > 0 ) { - // this is the first block and it has a custom size - if ( 8*m_block_element_count >= element_count_estimate ) - m_al_count = element_count_estimate; + if (element_count_estimate <= 8*m_block_element_count ) + m_al_count = element_count_estimate; // 1st block will have room for element_count_estimate elements else - m_al_count = 8*m_block_element_count; // first block will be large + m_al_count = 8*m_block_element_count; // 1st block will be 8xlarger than subsequent blocks, but not as huge as requested } else { + // 1st block is the same size as subsequent blocks m_al_count = m_block_element_count; } return true; } +bool ON_FixedSizePool::CreateForExperts( + size_t sizeof_element, + size_t maximum_element_count_estimate, + size_t minimum_block2_element_capacity +) +{ + if (m_sizeof_element != 0 || 0 != m_first_block) + { + ON_ERROR("ON_FixedSizePool::Create - called on a pool that is in use."); + return false; + } + + memset(this, 0, sizeof(*this)); + + if (sizeof_element <= 0) + { + ON_ERROR("Invalid parameter: sizeof_element <= 0."); + return false; + } + + const size_t default_block_capacity = ON_FixedSizePool::DefaultElementCapacityFromSizeOfElement(sizeof_element); + if (default_block_capacity <= 0 || default_block_capacity* sizeof_element <= 0) + { + ON_ERROR("Invalid parameter: sizeof_element is too large for a fixed size pool."); + return false; + } + + if (maximum_element_count_estimate < 0) + { + ON_ERROR("Invalid parameter: block1_element_count < 0."); + return false; + } + + if (0 == maximum_element_count_estimate) + minimum_block2_element_capacity = 0; + else if (minimum_block2_element_capacity < 0) + { + ON_ERROR("Invalid parameter: minimum_block2_element_capacity < 0."); + return false; + } + + + size_t block1_capacity = 0; // 1st block will have room for m_al_count elements. + size_t block2_capacity = 0; // 2nd and subsequent blocks will have room m_block_element_count elements. + + if (maximum_element_count_estimate > 0) + { + if (maximum_element_count_estimate <= 4 * default_block_capacity) + { + // We should be able to reliably allocate a contiguous memory space + // that will hold maximum_element_count_estimate elements. + block1_capacity = maximum_element_count_estimate; + + // The caller claims that maximum_element_count_estimate is a tight upper bound + // on the number of elements to be allocated. + // If they underestimated, keep subsequent blocks small assuming that they + // underestimated by only a little bit. + block2_capacity = (block1_capacity + 9) / 10; + if (block2_capacity <= 0) + block2_capacity = 1; + if (block2_capacity < minimum_block2_element_capacity) + block2_capacity = minimum_block2_element_capacity; + } + else + { + // The value maximum_element_count_estimate is too big for + // a reasonably sized chuck of contiguous memory space. + // + // Find a way to allocate maximum_element_count_estimate elements from + // multiple blocks and not waste a bunch of memory when + // maximum_element_count_estimate is tight upper bound on the + // number of element that will actually be allocated. + // + // minimum_block2_element_capacity is intentionally being ignored + // in this case. + size_t n = maximum_element_count_estimate / default_block_capacity; + if (n > 0) + { + // We will use n blocks of block1_capacity elements to deliver + // maximum_element_count_estimate elements. These + // blocks will be reasonably sized and easy to allocate. + block1_capacity = maximum_element_count_estimate / n; + if (n * block1_capacity < maximum_element_count_estimate) + ++block1_capacity; + block2_capacity = block1_capacity; + } + } + } + + ////////////////////////////// + // Initialize this pool + + m_sizeof_element = sizeof_element; + + // 1st block will have room for m_al_count elements. + m_al_count = block1_capacity > 0 ? block1_capacity : default_block_capacity; + + // 2nd and subsequent blocks will have room m_block_element_count elements. + m_block_element_count = block2_capacity > 0 ? block2_capacity : default_block_capacity; + + return true; +} + void ON_FixedSizePool::ReturnAll() { if ( 0 != m_first_block ) @@ -721,6 +865,41 @@ size_t ON_FixedSizePool::ElementIndex(const void* element_pointer) const return ON_MAX_SIZE_T; } +bool ON_FixedSizePool::InPool( + const void* p +) const +{ + if (nullptr != p) + { + const char* block; + const char* block_end; + const char* next_block; + const char* ptr = (const char*)p; + for (block = (const char*)m_first_block; 0 != block; block = next_block) + { + if (block == m_al_block) + { + // After a ReturnAll(), a multi-block fsp has unused blocks after m_al_block. + // Searching must terminate at m_al_block. + next_block = nullptr; + block_end = (const char*)m_al_element_array; + block += (2 * sizeof(void*)); + } + else + { + next_block = *((const char**)block); + block += sizeof(void*); + block_end = *((const char**)(block)); + block += sizeof(void*); + } + if (ptr >= block && ptr < block_end) + return true; + } + } + + return false; +} + void* ON_FixedSizePool::ElementFromId( size_t id_offset, unsigned int id diff --git a/opennurbs_fsp.h b/opennurbs_fsp.h index 81993aec..d4e0a5af 100644 --- a/opennurbs_fsp.h +++ b/opennurbs_fsp.h @@ -40,15 +40,41 @@ public: ON_FixedSizePool(ON_FixedSizePool&&); ON_FixedSizePool& operator=(ON_FixedSizePool&&); #endif - + + /* Description: Create a fixed size memory pool. Parameters: - sizeof_element - [in] + sizeof_element - [in] number of bytes in each element. This parameter must be greater than zero. - In general, use sizeof(element type). If you pass a "raw" number as - sizeof_element, then be certain that it is the right size to insure the + In general, use sizeof(element type). If you pass a "raw" number as + sizeof_element, then be certain that it is the right size to insure the + fields in your elements will be properly aligned. + Remarks: + You must call Create() on an unused ON_FixedSizePool or call Destroy() + before calling create. + Returns: + True if successful and the pool can be used. + See Also + CreateForExperts(). + */ + bool Create( + size_t sizeof_element + ); + + /* + Description: + Create a fixed size memory pool. + If you have a decent estimate of how many elements you need, + CreateForExperts() is a typically a better choice. + Otherwise, Create(sizeof_element) is typically the best option. + + Parameters: + sizeof_element - [in] + number of bytes in each element. This parameter must be greater than zero. + In general, use sizeof(element type). If you pass a "raw" number as + sizeof_element, then be certain that it is the right size to insure the fields in your elements will be properly aligned. element_count_estimate - [in] (0 = good default) If you know how many elements you will need, pass that number here. @@ -58,21 +84,102 @@ public: If block_element_capacity is zero, Create() will calculate a block size that is efficent for most applications. If you are an expert user and want to specify the number of elements per block, - then pass the number of elements per block here. When - block_element_capacity > 0 and element_count_estimate > 0, the first + then pass the number of elements per block here. When + block_element_capacity > 0 and element_count_estimate > 0, the first block will have a capacity of at least element_count_estimate; in this case do not ask for extraordinarly large amounts of contiguous heap. + Remarks: You must call Create() on an unused ON_FixedSizePool or call Destroy() before calling create. Returns: True if successful and the pool can be used. */ - bool Create( + bool Create( size_t sizeof_element, size_t element_count_estimate, size_t block_element_capacity - ); + ); + + + /* + Description: + Create a fixed size memory pool. + Parameters: + sizeof_element - [in] + number of bytes in each element. This parameter must be greater than zero. + In general, use sizeof(element type). If you pass a "raw" number as + sizeof_element, then be certain that it is the right size to insure the + fields in your elements will be properly aligned. + + maximum_element_count_estimate - [in] (0 = good default) + If you have a tight upper bound on the number of elements you need + from this fixed size pool, call Create(sizeof_element) instead. + + If the description of this parameter is confusing to you, + call Create(sizeof_element) instead. + + If you have a tight upper bound on how many elements you will need, + pass that number here. When maximum_element_count_estimate > 0, the + initial memory blocks in the fixed size pool will be sized to efficiently + deliver maximum_element_count_estimate elements. + The fixed block pool can become inefficient when maximum_element_count_estimate + is a gross overestimate or a slight underestimate of the actual number of + elements that get allocated. + + minimum_block2_element_capacity - [in] (0 = good default) + If the description below is confusing, pass 0. + If maximum_element_count_estimate = 0, this parameter is ignored. + If maximum_element_count_estimate > 0 and you have an excellent choice + for a lower bound on the number of elements per block for unexpected allocations + of more than maximum_element_count_estimate elements, then pass that value for + minimum_block2_element_capacity. + + Remarks: + You must call Create() or CreateEx() on an unused ON_FixedSizePool or call Destroy() + before calling create. + Returns: + True if successful and the pool can be used. + */ + bool CreateForExperts( + size_t sizeof_element, + size_t maximum_element_count_estimate, + size_t minimum_block2_element_capacity + ); + + static size_t DefaultElementCapacityFromSizeOfElement(size_t sizeof_element); + + /* + Description: + Tool for debugging pool use when tuning block size and block capacity. + Returns: + Total operating system heap memory (in bytes) used by this ON_FixedSizePool. + Remarks: + SizeOfPool() = SizeOfAllocatedElements() + SizeOfUnusedElements(). + */ + size_t SizeOfPool() const; + + /* + Description: + Tool for debugging pool use when tuning block size and block capacity. + Returns: + Operating system heap memory (in bytes) that are used by active pool elements. + Remarks: + SizeOfPool() = SizeOfActiveElements() + SizeOfUnusedElements(). + */ + size_t SizeOfActiveElements() const; + + /* + Description: + Tool for debugging pool use when tuning block size and block capacity. + Returns: + Operating system heap memory (in bytes) that has been reserved but is not + currently used by active elements. + Remarks: + SizeOfPool() = SizeOfActiveElements() + SizeOfUnusedElements(). + */ + size_t SizeOfUnusedElements() const; + /* Returns: @@ -237,6 +344,17 @@ public: const void* element_pointer ) const; + /* + Parameters: + p - [in] + pointer to test + Returns: + True if p points to memory in this pool. + */ + bool InPool( + const void* pointer + ) const; + /* Description: If you are certain that all elements in the pool (active and returned) diff --git a/opennurbs_glyph_outline.cpp b/opennurbs_glyph_outline.cpp index e764c2c8..75515ffd 100644 --- a/opennurbs_glyph_outline.cpp +++ b/opennurbs_glyph_outline.cpp @@ -117,6 +117,37 @@ void ON_Outline::Reverse() } } +const bool Internal_FigureBoxesAreDisjoint( + const ON_BoundingBox& a, + const ON_BoundingBox& b +) +{ + // figure boxes are 2d - ignore z. + // figures are closed loops, so we can use <= and >= instead of < and > + if (a.m_min[0] >= b.m_max[0]) + return true; + if (a.m_max[0] <= b.m_min[0]) + return true; + if (a.m_min[1] >= b.m_max[1]) + return true; + if (a.m_max[1] <= b.m_min[1]) + return true; + return false; +} + +static int Internal_CompareAreaEstimate(ON_OutlineFigure* const* lhs, ON_OutlineFigure* const* rhs) +{ + // Used to sort the figures_sorted_by_size[] array which is constructed in a way + // that we know all elements are not nullptr and have valid AreaEstimates(). + const double lhs_area = fabs((*lhs)->AreaEstimate()); + const double rhs_area = fabs((*rhs)->AreaEstimate()); + if (lhs_area > rhs_area) + return -1; // largest areas are before smaller areas + if (lhs_area < rhs_area) + return 1; // largest areas are before smaller areas + return 0; +} + void ON_Outline::SortFigures( ON_OutlineFigure::Orientation outer_loop_orientation ) @@ -139,14 +170,12 @@ void ON_Outline::SortFigures( if (figure_count <= 0) return; - const ON_OutlineFigure::Orientation inner_loop_orientation = (ON_OutlineFigure::Orientation::Clockwise == outer_loop_orientation) ? ON_OutlineFigure::Orientation::CounterClockwise : ON_OutlineFigure::Orientation::Clockwise; - ON_SimpleArray outer_figures(figure_count); - ON_SimpleArray inner_figures(figure_count); + ON_SimpleArray sorted_figures(figure_count); ON_SimpleArray ignored_figures(figure_count); for (int i = 0; i < figure_count; i++) @@ -169,10 +198,7 @@ void ON_Outline::SortFigures( || inner_loop_orientation == figure_orientation) ) { - if (outer_loop_orientation == ptr->FigureOrientation()) - outer_figures.Append(ptr); - else - inner_figures.Append(ptr); + sorted_figures.Append(ptr); continue; } @@ -187,8 +213,74 @@ void ON_Outline::SortFigures( ignored_figures.Append(ptr); } - unsigned int outer_count = outer_figures.UnsignedCount(); - unsigned int inner_count = inner_figures.UnsignedCount(); + const unsigned int sorted_figure_count = sorted_figures.UnsignedCount(); + + // Sort figures so the ones with largest included area are first. + sorted_figures.QuickSort(Internal_CompareAreaEstimate); + + // As we look at each element of sorted_figures[], it gets assigned to + // outer_figures[] or inner_figures[]. If it is assigned to inner_figures[], + // the smallest figure that contains it is assigned to inner_figures_parent[]. + // + // Over the years, many more complicated approaches to sorting were tried, + // all of which attempted to take into account the orientations from the font file. + // It is so common for the orientations in the font files to be wrong, + // that ignoring them is the most efficient and reliable approach to date. + // It is extremly common for outer figures to overlap. + ON_SimpleArray outer_figures(sorted_figure_count); + ON_SimpleArray inner_figures(sorted_figure_count); + ON_SimpleArray inner_figures_parent(sorted_figure_count); + + for ( unsigned k = 0; k < sorted_figure_count; ++k) + { + ON_OutlineFigure* f = sorted_figures[k]; + + // This for(int k0; ...) loop finds the smallest larger figure that contains f and uses + // the orientation of this containing loop to determine the orientation of f. + ON_OutlineFigure* parent_outer_figure = nullptr; + for (unsigned k0 = 0; k0 < k; ++k0) + { + // Given our assumptions, it is impossible for bigger_f to be inside of f. + ON_OutlineFigure* bigger_f = sorted_figures[k0]; + if (false == f->IsInsideOf(bigger_f, false)) + continue; + + if (outer_loop_orientation == bigger_f->FigureOrientation()) + { + // f is inside of bigger_f and bigger_f is an outer loop. + parent_outer_figure = bigger_f; + } + else if (inner_loop_orientation == bigger_f->FigureOrientation()) + { + // f is inside of bigger_f and bigger_f is an inner loop. + // So f is a nested outer (like the registered trademark glyph) + parent_outer_figure = nullptr; + } + } + + if (nullptr == parent_outer_figure) + { + outer_figures.Append(f); + if (inner_loop_orientation == f->FigureOrientation()) + { + // convert this figure to an outer. + f->ReverseFigure(); + } + } + else + { + inner_figures.Append(f); + inner_figures_parent.Append(parent_outer_figure); + if (outer_loop_orientation == f->FigureOrientation()) + { + // convert this figure to an inner + f->ReverseFigure(); + } + } + } + + const unsigned outer_count = outer_figures.UnsignedCount(); + const unsigned inner_count = inner_figures.UnsignedCount(); if ( 0 == outer_count ) { @@ -196,210 +288,30 @@ void ON_Outline::SortFigures( return; } - if (0 == inner_count ) + if (0 == inner_count) { return; } - 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) + // Sort figures by outer followed by its inners. + sorted_figures.SetCount(0); + for (unsigned i = 0; i < outer_count; ++i) { - const unsigned int sorted_figures_count0 = sorted_figures.UnsignedCount(); - for (unsigned int outer_dex = 0; outer_dex < outer_count; outer_dex++) + ON_OutlineFigure* outer_figure = outer_figures[i]; + if (nullptr == outer_figure) + continue; + sorted_figures.Append(outer_figure); + for (unsigned j = 0; j < inner_count; ++j) { - 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++) + if (outer_figure == inner_figures_parent[j]) { - 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; - 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); + ON_OutlineFigure* inner_figure = inner_figures[j]; + inner_figures[j] = nullptr; + inner_figures_parent[j] = nullptr; + if (nullptr != inner_figure) + sorted_figures.Append(inner_figure); } } - 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) - { - 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. @@ -804,6 +716,297 @@ double ON_OutlineFigure::BoxArea() const return bbox_area; } +unsigned ON_OutlineFigure::GetUpToFourPointsOnFigure(ON_2fPoint p[4]) const +{ + if (nullptr == p) + return 0; + + unsigned point_count = 0; + const ON__UINT32 end_dex = Internal_FigureEndDex(false); + if (end_dex > 0) + { + // p[0] = point at the start + const ON_2fPoint p0 = this->m_points[0].m_point; + p[point_count++] = p0; + + + // p[1] = point between start and middle + ON_2fPoint prev_p = p0; + unsigned i1 = end_dex / 2; + if (i1 >= 2) + { + for (ON__UINT32 i = i1 - 1; i > 0; --i) + { + if (false == this->m_points[i].IsOnFigure()) + continue; + const ON_2fPoint candidate_p = this->m_points[i].m_point; + if (prev_p != candidate_p) + { + prev_p = candidate_p; + p[point_count++] = candidate_p; + break; + } + } + } + + // p[2] = point between middle and end + for (/*empty init*/; i1 <= end_dex; ++i1) + { + if (false == this->m_points[i1].IsOnFigure()) + continue; + const ON_2fPoint candidate_p = this->m_points[i1].m_point; + if (prev_p != candidate_p) + { + prev_p = candidate_p; + p[point_count++] = candidate_p; + break; + } + } + + // p[3] = point close to end + for (ON__UINT32 i = end_dex; i > i1; --i) + { + if (false == this->m_points[i].IsOnFigure()) + continue; + const ON_2fPoint candidate_p = this->m_points[i].m_point; + if (prev_p != candidate_p && p0 != candidate_p) + { + p[point_count++] = candidate_p; + break; + } + } + } + + for (unsigned i = point_count; i < 4; ++i) + p[i] = ON_2fPoint::NanPoint; + return point_count; +} + +static bool Internal_ExtraInsideOfPolylineText( + const ON_OutlineFigure* outer_figure, + const ON_OutlineFigure* inner_figure +) +{ + // This function is called when an extra test is needed to be certain + // that outer_pline contains inner_pline. Generating the input + // for this text and performing this test is computationally expensive. + // It is called sparingly. + + if (nullptr == outer_figure || nullptr == inner_figure) + return false; + + ON_SimpleArray outer_pline; + outer_figure->GetPolyline(0.0, outer_pline); + const unsigned outer_count = outer_pline.UnsignedCount(); + if (outer_count < 4) + return false; + + ON_SimpleArray inner_pline; + inner_figure->GetPolyline(0.0, inner_pline); + const unsigned inner_count = inner_pline.UnsignedCount(); + if (inner_count < 2) + return false; + + const ON_2dPoint* a = outer_pline.Array(); + const ON_2dPoint* b = inner_pline.Array(); + + // Bmin, Bmax = bounding box of inner_pline. + ON_2dPoint Bmin = b[0]; + ON_2dPoint Bmax = Bmin; + double c; + for (unsigned j = 1; j < inner_count; ++j) + { + c = b[j].x; + if (c < Bmin.x) + Bmin.x = c; + else if (c > Bmax.x) + Bmax.x = c; + c = b[j].y; + if (c < Bmin.y) + Bmin.y = c; + else if (c > Bmax.y) + Bmax.y = c; + } + + ON_2dPoint Amin; + ON_2dPoint Amax; + ON_2dPoint A[2] = { ON_2dPoint::NanPoint,a[0] }; + ON_2dPoint B[2] = { ON_2dPoint::NanPoint,ON_2dPoint::NanPoint }; + double alpha[3] = { ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN }; + for (unsigned i = 1; i < outer_count; ++i) + { + A[0] = A[1]; + A[1] = a[i]; + if (A[0] == A[1]) + continue; + + // Amin,Amax = bounding box of outer line segment + + if (A[0].x <= A[1].x) + { + Amin.x = A[0].x; + Amax.x = A[1].x; + } + else + { + Amin.x = A[1].x; + Amax.x = A[0].x; + } + if (Amax.x < Bmin.x) + continue; + if (Amin.x > Bmax.x) + continue; + + if (A[0].y <= A[1].y) + { + Amin.y = A[0].y; + Amax.y = A[1].y; + } + else + { + Amin.y = A[1].y; + Amax.y = A[0].y; + } + if (Amax.y < Bmin.y) + continue; + if (Amin.y > Bmax.y) + continue; + + alpha[0] = ON_DBL_QNAN; + + B[1] = b[0]; + for (unsigned j = 1; j < inner_count; ++j) + { + B[0] = B[1]; + B[1] = b[j]; + if (B[0] == B[1]) + continue; + + // line segment bounding box check + if (B[0].x < Amin.x && B[1].x < Amin.x) + continue; + if (B[0].y < Amin.y && B[1].y < Amin.y) + continue; + if (B[0].x > Amax.x && B[1].x > Amax.x) + continue; + if (B[0].y > Amax.y && B[1].y > Amax.y) + continue; + + if (alpha[0] != alpha[0]) + { + // need to initialize alpha[3] + alpha[0] = A[1].y - A[0].y; + alpha[1] = A[0].x - A[1].x; + alpha[2] = A[1].x * A[0].y - A[0].x * A[1].y; + }; + + double h[2] = { + alpha[0] * B[0].x + alpha[1] * B[0].y + alpha[2], + alpha[0] * B[1].x + alpha[1] * B[1].y + alpha[2] + }; + if (h[0] < 0.0 && h[1] < 0.0) + continue; // B[0] and B[1] on same side of infinte line through A[0],A[1] + if (h[0] > 0.0 && h[1] > 0.0) + continue; // B[0] and B[1] on same side of infinte line through A[0],A[1] + + const double beta[3] = + { + B[1].y - B[0].y, + B[0].x - B[1].x, + B[1].x * B[0].y - B[0].x * B[1].y + }; + + h[0] = beta[0] * B[0].x + beta[1] * B[0].y + beta[2]; + h[1] = beta[0] * B[1].x + beta[1] * B[1].y + beta[2]; + if (h[0] < 0.0 && h[1] < 0.0) + continue; // A[0] and A[1] on same side of infinte line through B[0],B[1] + if (h[0] > 0.0 && h[1] > 0.0) + continue; // A[0] and A[1] on same side of infinte line through B[0],B[1] + + // The line segment A[0],A[1] and line segment B[0],B[1] intersect someplace. + // The location of the intersection doesn't matter, even if it's one of the end points. + return false; + } + } + + return true; +} + +bool ON_OutlineFigure::IsInsideOf( + const ON_OutlineFigure* outer_figure, + bool bPerformExtraChecking +) const +{ + if (nullptr == outer_figure) + return false; + + const ON_OutlineFigure::Orientation outer_orientation = outer_figure->FigureOrientation(); + if (ON_OutlineFigure::Orientation::Clockwise != outer_orientation && ON_OutlineFigure::Orientation::CounterClockwise != outer_orientation) + return false; + + + const ON_BoundingBox this_bbox = this->BoundingBox(); + const ON_BoundingBox outer_bbox = outer_figure->BoundingBox(); + if (false == outer_bbox.Includes(this_bbox)) + { + // The bounding boxes are from the glyph outline control polygons. + // There are rare case when this f is inside of other_f + // but this_bbox is not contained in other_bbox. In practice, this + // is quite rare, but that's why "probably" is part of this function's name. + return false; + } + + const double outer_area = fabs(outer_figure->AreaEstimate()); + const double this_area = fabs(this->AreaEstimate()); + if (false == (0.0 < this_area && this_area < outer_area)) + return false; // this figure is too big to be inside of other_f + + // Check that the 3 standard test points on this are inside of other_f. + // Again, it is possible that the 3 test points are inside but some other + // point is outside. In practice this is rare and that possibility + // is why "probably" is in this function's name. + + const int outer_orientation_sign = ON_OutlineFigure::Orientation::CounterClockwise == outer_orientation ? 1 : -1; + + ON_2fPoint this_p[4]; + unsigned this_point_count = this->GetUpToFourPointsOnFigure(this_p); + + for (unsigned i = 0; i < this_point_count; ++i) + { + // check start point. + const int wn = outer_orientation_sign * outer_figure->WindingNumber(this_p[i]); + if (1 != wn) + { + // this_p[i] is not inside of other_f. + return false; + } + } + + if (0 == this_point_count) + return false; + + const ON_OutlineFigure::Orientation this_orientation = this->FigureOrientation(); + + if (outer_orientation == this_orientation || bPerformExtraChecking) + { + // The context that calls this function is sorting nested loops. + // The orientation of outer_figure has been decided and set at this point. + // When this orientation of this is not different, we need more checking + // to verify that the orientaion from the font definition file was really "wrong". + // The "A crossbar" in Bahnschrift U+00C5 is one of many cases that + // require this additional checking. More generally, glyphs with + // orientations set correctly and which use overlapping outer + // boundaries need this test to prevent incorrectly. This situation is + // common in fonts like Bahnschrift and fonts for Asian language scripts + // that have "brush stroke" boundaries that overlap. + if (false == Internal_ExtraInsideOfPolylineText(outer_figure, this)) + return false; + } + + return true; +} + + ON__UINT32 ON_OutlineFigure::UnitsPerEM() const { return m_units_per_em; @@ -2525,6 +2728,7 @@ bool ON_OutlineFigure::IsValidFigure( const ON_OutlineFigurePoint* a = m_points.Array(); + const ON_OutlineFigurePoint figure_start = a[0]; if (false == figure_start.IsBeginFigurePoint()) { diff --git a/opennurbs_internal_glyph.h b/opennurbs_internal_glyph.h index 107e9911..60991faa 100644 --- a/opennurbs_internal_glyph.h +++ b/opennurbs_internal_glyph.h @@ -71,8 +71,6 @@ public: // sorts nulls to end of lists static int CompareFontPointer(ON_Font const* const* lhs, ON_Font const* const* rhs); - static int CompareFontCharacteristicsHash(ON_Font const* const* lhs, ON_Font const* const* rhs); - /* Returns: 0: failure @@ -102,9 +100,19 @@ public: ); private: - ON_ManagedFonts(); + // The purpose of this nondefault constructor is to create ON_ManagedFonts::List + // in opennurbs_statics.cpp in a way that Apple's CLang will actually compile. + // The only instance of ON_ManagedFonts is ON_ManagedFonts::List. + ON_ManagedFonts(ON__UINT_PTR zero); + ~ON_ManagedFonts(); +private: + ON_ManagedFonts() = delete; + ON_ManagedFonts(const ON_ManagedFonts&) = delete; + ON_ManagedFonts& operator=(const ON_ManagedFonts&) = delete; + +private: /* Parameters: managed_font_metrics_in_font_design_units - [in] diff --git a/opennurbs_leader.cpp b/opennurbs_leader.cpp index 9e855989..ce0b99fe 100644 --- a/opennurbs_leader.cpp +++ b/opennurbs_leader.cpp @@ -274,6 +274,9 @@ bool ON_Leader::GetTextXform( if (dimstyle->LeaderHasLanding()) landing_length = dimstyle->LeaderLandingLength(); double text_gap = dimstyle->TextGap(); + + if (ON_TextMask::MaskFrame::RectFrame == dimstyle->MaskFrameType()) + text_gap = dimstyle->TextMask().MaskBorder(); double x_offset = dimscale * (landing_length + text_gap + textblock_width / 2.0); text_pt2 = text_pt2 + (tail_dir * x_offset); diff --git a/opennurbs_line.cpp b/opennurbs_line.cpp index d46102b8..22c0e5fc 100644 --- a/opennurbs_line.cpp +++ b/opennurbs_line.cpp @@ -678,6 +678,24 @@ bool ON_Triangle::GetTightBoundingBox( return rc; } +unsigned char ON_Triangle::LongestEdge() const +{ + double l0 = (m_V[1] - m_V[2]).LengthSquared(); + double l1 = (m_V[2] - m_V[0]).LengthSquared(); + double l2 = (m_V[0] - m_V[1]).LengthSquared(); + + return l1 < l2 ? ((l2 <= l0) ? 0 : 2) : ((l1 <= l0) ? 0 : 1); +} + +unsigned char ON_Triangle::ShortestEdge() const +{ + double l0 = (m_V[1] - m_V[2]).LengthSquared(); + double l1 = (m_V[2] - m_V[0]).LengthSquared(); + double l2 = (m_V[0] - m_V[1]).LengthSquared(); + + return l2 < l1 ? ((l2 < l0) ? 2 : 0) : ((l1 < l0) ? 1 : 0); +} + ON_Line ON_Triangle::Edge(int i) const { return ON_Line(m_V[(i + 1) % 3], m_V[(i + 2) % 3]); @@ -883,6 +901,75 @@ bool ON_Triangle::Translate(const ON_3dVector & delta) return Transform(T); } +void ON_Triangle::Split(unsigned char edge, ON_3dPoint pt, ON_Triangle& out_a, ON_Triangle& out_b) const +{ + switch (edge % 3) + { + case 0: + out_a.m_V[0] = m_V[0]; + out_a.m_V[1] = m_V[1]; + out_a.m_V[2] = pt; + out_b.m_V[0] = m_V[0]; + out_b.m_V[1] = pt; + out_b.m_V[2] = m_V[2]; + break; + case 1: + out_a.m_V[0] = m_V[0]; + out_a.m_V[1] = m_V[1]; + out_a.m_V[2] = pt; + out_b.m_V[0] = pt; + out_b.m_V[1] = out_b.m_V[1]; + out_b.m_V[2] = out_b.m_V[2]; + break; + default: //2 + out_a.m_V[0] = m_V[0]; + out_a.m_V[1] = pt; + out_a.m_V[2] = m_V[2]; + out_b.m_V[0] = pt; + out_b.m_V[1] = m_V[1]; + out_b.m_V[2] = m_V[2]; + break; + } +} + +void ON_Triangle::Flip(unsigned char edge) +{ + switch (edge % 3) + { + case 0: + std::swap(m_V[1], m_V[2]); + break; + case 1: + std::swap(m_V[2], m_V[0]); + break; + default: //2 + std::swap(m_V[0], m_V[1]); + break; + } +} + +void ON_Triangle::Spin(unsigned char move) +{ + ON_3dPoint tmp; + switch (move % 3) + { + case 0: + break; + case 1: + tmp = m_V[0]; + m_V[0] = m_V[2]; + m_V[2] = m_V[1]; + m_V[1] = tmp; + break; + default: //2 + tmp = m_V[0]; + m_V[0] = m_V[1]; + m_V[1] = m_V[2]; + m_V[2] = tmp; + break; + } +} + bool operator==(const ON_Triangle & a, const ON_Triangle & b) { diff --git a/opennurbs_line.h b/opennurbs_line.h index 016fa8e8..e8dc1b65 100644 --- a/opennurbs_line.h +++ b/opennurbs_line.h @@ -400,6 +400,16 @@ public: const ON_Xform* xform = nullptr ) const; + // Returns: + // Index of edge opposite to m_V[i] that is longest. + // When lenghts are equal, lowest index has priority. + unsigned char LongestEdge() const; + + // Returns: + // Index of edge opposite to m_V[i] that is shortest. + // When lenghts are equal, lowest index has priority. + unsigned char ShortestEdge() const; + // Returns: // Edge opposite m_V[i] // Specifically, @@ -549,6 +559,28 @@ public: const ON_3dVector& delta ); + // Description: + // Split the triangles into two, by choosing an edge and a new point that will appear along the edge. + // Parameters: + // edge - [in] Edge index as defined in Edge() + // pt - [in] Point to add as splitter along edge + // out_a - [out] First triangle + // out_b - [out] Second triangle + void Split(unsigned char edge, ON_3dPoint pt, ON_Triangle& out_a, ON_Triangle& out_b) const; + + // Description: + // Flip the normal of the triangle, by swapping the points of an edge. + // Parameters: + // edge - [in] The edge, as defined in the Edge() method. I.e., edge 0 swaps m_V[1] and m_V[2] + void Flip(unsigned char edge = 0); + + // Description: + // Circle the order of points in the triangle, without any influence to any geometric property. + // Parameters: + // move - [in] Amounts of rotations in the order of the three points. + // By means of examples, "move" of 1 will move m_V[0] to m_V[1], + // m_V[1] to m_V[2] and m_V[2] to m_V[0]. + void Spin(unsigned char move); public: ON_3dPoint m_V[3]; // verticies diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp index e12a6b18..b1099f86 100644 --- a/opennurbs_material.cpp +++ b/opennurbs_material.cpp @@ -3657,7 +3657,12 @@ ON__UINT32 ON_TextureMapping::MappingCRC() const const ON_2fPoint* tex = mesh->m_T.Array(); crc32 = ON_CRC32(crc32,mesh->m_T.Count()*sizeof(tex[0]),tex); } - } + // January 7th 2021 Jussi + // Added crc of origin to change the mapping crc to tricker re-mapping + // of custom mesh mapped objects. This is to make the improvements to + // CMeshClosestPointMapper::MatchFaceTC effective on existing models. + crc32 = ON_CRC32(crc32, sizeof(ON_3dPoint), &ON_3dPoint::UnsetPoint); + } break; case ON_TextureMapping::TYPE::brep_mapping_primitive: diff --git a/opennurbs_math.cpp b/opennurbs_math.cpp index 9d2158d6..babb5422 100644 --- a/opennurbs_math.cpp +++ b/opennurbs_math.cpp @@ -4105,8 +4105,8 @@ bool ON_EvPrincipalCurvatures( const ON_3dVector& N, // unit normal (use TL_EvNormal()) double* gauss, // = Gaussian curvature = kappa1*kappa2 double* mean, // = mean curvature = (kappa1+kappa2)/2 - double* kappa1, // = largest principal curvature value (may be negative) - double* kappa2, // = smallest principal curvature value (may be negative) + double* kappa1, // = largest (in absolute value) principal curvature (may be negative) + double* kappa2, // = smallest (in absolute value) principal curvature(may be negative) ON_3dVector& K1, // kappa1 unit principal curvature direction ON_3dVector& K2 // kappa2 unit principal curvature direction // output K1,K2,N is right handed frame @@ -4129,8 +4129,8 @@ bool ON_EvPrincipalCurvatures( const ON_3dVector& N, // unit normal (use TL_EvNormal()) double* gauss, // = Gaussian curvature = kappa1*kappa2 double* mean, // = mean curvature = (kappa1+kappa2)/2 - double* kappa1, // = largest principal curvature value (may be negative) - double* kappa2, // = smallest principal curvature value (may be negative) + double* kappa1, // = largest (in absolute value) principal curvature (may be negative) + double* kappa2, // = smallest (in absolute value) principal curvature (may be negative) ON_3dVector& K1, // kappa1 unit principal curvature direction ON_3dVector& K2 // kappa2 unit principal curvature direction // output K1,K2,N is right handed frame @@ -4825,7 +4825,7 @@ bool ON_SymTriDiag3x3EigenSolver(double A, double B, double C, double* e3, ON_3dVector& E3); static -bool TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV); +bool ON_TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV); /* Description: @@ -4996,7 +4996,7 @@ bool ON_SymTriDiag3x3EigenSolver(double A, double B, double C, double e[3] = { D,E,0 }; ON_Matrix V(3, 3); - bool rc = TriDiagonalQLImplicit(d, e, 3, &V); + bool rc = ON_TriDiagonalQLImplicit(d, e, 3, &V); if (rc) { if (e1) *e1 = d[0]; @@ -5025,7 +5025,7 @@ n - [in] matrix is n by n pV - [out] If not nullptr the it should be an n by n matix. The kth column will be a normalized eigenvector of d[k] */ -bool TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV) +bool ON_TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV) { /* Debug code ON_SimpleArray OrigD(n); @@ -5142,6 +5142,66 @@ bool TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV) return true; } +unsigned ON_GreatestCommonDivisor( + unsigned a, + unsigned b +) +{ + // binary GCD algorithm + // https://en.wikipedia.org/wiki/Binary_GCD_algorithm + unsigned s = 0; + while (a != 0 && b != 0) + { + if (a == b) + return a << s; //g* a; + if ((a & 1)) + { + if ((b & 1)) + { + if (a > b) + a = (a - b) >> 1; + else + { + const unsigned t = a; + a = (b - a) >> 1; + b = t; + } + } + else + b >>= 1; + } + else if ((b & 1)) + a >>= 1; + else + { + a >>= 1; + b >>= 1; + ++s; // g <<= 1; + } + } + if (a == 0) + return b << s; + if (b == 0) + return a << s; + + return 0; +} + +unsigned ON_LeastCommonMultiple( + unsigned a, + unsigned b +) +{ + if (0 == a || 0 == b) + return 0; + + const unsigned gcd = ON_GreatestCommonDivisor(a, b); + a /= gcd; + b /= gcd; + + // 0 is returned when the a*b*gcd overflows unsigned storage. + return (a * b < ON_UINT_MAX / gcd) ? (a * b * gcd) : 0U; +} diff --git a/opennurbs_math.h b/opennurbs_math.h index 2b3c6abd..c82aca09 100644 --- a/opennurbs_math.h +++ b/opennurbs_math.h @@ -1436,8 +1436,8 @@ bool ON_EvPrincipalCurvatures( const ON_3dVector&, // N, // unit normal to surface (use ON_EvNormal()) double*, // gauss, // = Gaussian curvature = kappa1*kappa2 double*, // mean, // = mean curvature = (kappa1+kappa2)/2 - double*, // kappa1, // = largest principal curvature value (may be negative) - double*, // kappa2, // = smallest principal curvature value (may be negative) + double*, // kappa1, // = largest (in absolute value) principal curvature value (may be negative) + double*, // kappa2, // = smallest (in absolute value) principal curvature value (may be negative) ON_3dVector&, // K1, // kappa1 unit principal curvature direction ON_3dVector& // K2 // kappa2 unit principal curvature direction // output K1,K2,N is right handed frame @@ -1453,8 +1453,8 @@ bool ON_EvPrincipalCurvatures( const ON_3dVector&, // N, // unit normal to surface (use ON_EvNormal()) double*, // gauss, // = Gaussian curvature = kappa1*kappa2 double*, // mean, // = mean curvature = (kappa1+kappa2)/2 - double*, // kappa1, // = largest principal curvature value (may be negative) - double*, // kappa2, // = smallest principal curvature value (may be negative) + double*, // kappa1, // = largest (in absolute value) principal curvature value (may be negative) + double*, // kappa2, // = smallest (in absolute value) principal curvature value (may be negative) ON_3dVector&, // K1, // kappa1 unit principal curvature direction ON_3dVector& // K2 // kappa2 unit principal curvature direction // output K1,K2,N is right handed frame @@ -2372,4 +2372,35 @@ ON_DECL bool ON_IsConvexPolyline( bool bStrictlyConvex ); +/// +/// +/// +/// +/// +/// +/// If a > 0 or b > 0, then the greatest common divisor of a and b is returned (nonzero). +/// Note that if n > 0, then gcd(0,n) = gcd(n,0) = n and gcd(1,n) = gcd(n,1) = 1. +/// If a = 0 and b = 0, then the greatest common divisor is not defined and 0 is returned. +/// +ON_DECL unsigned ON_GreatestCommonDivisor( + unsigned a, + unsigned b +); + +/// +/// The least common multiple of a and b is (a/gcd)*(b/gcd)*(gcd), where gcd = greatest common divisor of and b. +/// +/// +/// +/// +/// If a $gt; 0 and b > and the least common multiple of a and b <= ON_UINT_MAX, then the +/// least common multiple of a and b is returned. +/// Otherwise 0 is returned. +/// +ON_DECL unsigned ON_LeastCommonMultiple( + unsigned a, + unsigned b +); + + #endif diff --git a/opennurbs_mesh.cpp b/opennurbs_mesh.cpp index 1d3533f8..c7c78cb2 100644 --- a/opennurbs_mesh.cpp +++ b/opennurbs_mesh.cpp @@ -475,13 +475,24 @@ unsigned int ON_Mesh::SizeOf() const sz += m_N.SizeOfArray(); sz += m_FN.SizeOfArray(); sz += m_T.SizeOfArray(); + sz += m_TC.SizeOfArray(); + for (unsigned i = 0; i < m_TC.UnsignedCount(); ++i) + sz += m_TC[i].m_T.SizeOfArray(); sz += m_S.SizeOfArray(); sz += m_K.SizeOfArray(); sz += m_C.SizeOfArray(); + sz += m_H.SizeOfArray(); sz += m_top.m_topv_map.SizeOfArray(); sz += m_top.m_topv.SizeOfArray(); sz += m_top.m_tope.SizeOfArray(); sz += m_top.m_topf.SizeOfArray(); + if (nullptr != m_mesh_parameters) + sz += sizeof(*m_mesh_parameters); + if (nullptr != m_partition) + { + sz += sizeof(*m_partition); + sz += m_partition->m_part.SizeOfArray(); + } return sz; } @@ -1350,6 +1361,7 @@ void ON_Mesh::Dump( ON_TextLog& dump ) const dump.Print("m_srf_domain: (%g,%g)x(%g,%g)\n",m_srf_domain[0][0],m_srf_domain[0][1],m_srf_domain[1][0],m_srf_domain[1][1]); dump.Print("m_srf_scale: %g,%g\n",m_srf_scale[0],m_srf_scale[0]); dump.Print("m_Ttag:\n"); dump.PushIndent(); m_Ttag.Dump(dump); dump.PopIndent(); + dump.Print( ON_wString(L"Memory used: ") + ON_wString::ToMemorySize(this->SizeOf()) + ON_wString(L"\n") ); dump.PushIndent(); @@ -6274,6 +6286,12 @@ double ON_MeshParameters::MeshDensityAsPercentage(double normalized_mesh_density } double ON_MeshParameters::MeshDensity() const +{ + const bool bIgnoreSubDParameters = false; + return MeshDensity(bIgnoreSubDParameters); +} + +double ON_MeshParameters::MeshDensity(bool bIgnoreSubDParameters) const { for (;;) { @@ -6290,18 +6308,22 @@ double ON_MeshParameters::MeshDensity() const break; if (false == (this->m_refine_angle_radians == 0.0)) break; - const ON_SubDDisplayParameters subdp = this->SubDDisplayParameters(); - if ( subdp.DisplayDensityIsAbsolute() ) - break; // mesh dialog "slider" UI is always controls adaptive subd display density - if (subdp.DisplayDensity(ON_SubD::Empty) != ON_SubDDisplayParameters::CreateFromMeshDensity(candidate_density).DisplayDensity(ON_SubD::Empty)) - break; + + if (false == bIgnoreSubDParameters) + { + const ON_SubDDisplayParameters subdp = this->SubDDisplayParameters(); + if (subdp.DisplayDensityIsAbsolute()) + break; // mesh dialog "slider" UI is always controls adaptive subd display density + if (subdp.DisplayDensity(ON_SubD::Empty) != ON_SubDDisplayParameters::CreateFromMeshDensity(candidate_density).DisplayDensity(ON_SubD::Empty)) + break; + } // Now build one with the candidate_density slider value const ON_MeshParameters candidate_mp = ON_MeshParameters::CreateFromMeshDensity(candidate_density); if (candidate_mp.RelativeTolerance() != candidate_density) break; - if (candidate_mp.GeometrySettingsHash() != GeometrySettingsHash()) + if (candidate_mp.GeometrySettingsHash(bIgnoreSubDParameters) != GeometrySettingsHash(bIgnoreSubDParameters)) break; // These mesh parameters will create the same mesh geometry as ON_MeshParameters::CreateFromMeshDensity(). @@ -6528,7 +6550,14 @@ void ON_MeshParameters::Internal_AccumulatePangolinParameters( sha1.AccumulateInteger32(m_smoothing_passes); } + ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash() const +{ + bool bIgnoreSubDParameters = false; + return GeometrySettingsHash(bIgnoreSubDParameters); +} + +ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash(bool bIgnoreSubDParameters) const { // Please discuss any changes with Dale Lear @@ -6546,7 +6575,7 @@ ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash() const // match what was used to create an existing mesh, a remesh is extremely wasteful, // ClosedObjectPostProcess is added/removed as needed and is a hack to cover up core bugs. - if (m_geometry_settings_hash.IsZeroDigest()) + if (bIgnoreSubDParameters || m_geometry_settings_hash.IsZeroDigest()) { ON_SHA1 sha1; sha1.AccumulateBool(m_bSimplePlanes); @@ -6569,8 +6598,11 @@ ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash() const sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_grid_amplification,1.0)); sha1.AccumulateUnsigned32(m_face_type); - // SubD meshing parameters - sha1.AccumulateBytes(&m_subd_mesh_parameters_as_char, sizeof(m_subd_mesh_parameters_as_char)); + if (false == bIgnoreSubDParameters) + { + // SubD meshing parameters + sha1.AccumulateBytes(&m_subd_mesh_parameters_as_char, sizeof(m_subd_mesh_parameters_as_char)); + } if (ON_UuidIsNotNil(m_mesher_id)) { @@ -6584,6 +6616,9 @@ ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash() const Internal_AccumulatePangolinParameters(ON_MeshParameters::DefaultMesh, sha1); } + if (bIgnoreSubDParameters) + return sha1.Hash(); + m_geometry_settings_hash = sha1.Hash(); } return m_geometry_settings_hash; @@ -6714,17 +6749,26 @@ bool ON_MeshParameters::Write( ON_BinaryArchive& file ) const return rc; } +/*ON_MeshParameters::Type GeometrySettingsType( + bool bIgnoreSubDParameters + ) const*/ enum ON_MeshParameters::Type ON_MeshParameters::GeometrySettingsType() const { - const ON_SHA1_Hash mp_hash = GeometrySettingsHash(); + const bool bIgnoreSubDParameters = false; + return GeometrySettingsType(bIgnoreSubDParameters); +} - if ( mp_hash == ON_MeshParameters::DefaultMesh.GeometrySettingsHash()) +enum ON_MeshParameters::Type ON_MeshParameters::GeometrySettingsType(bool bIgnoreSubDParameters) const +{ + const ON_SHA1_Hash mp_hash = GeometrySettingsHash(bIgnoreSubDParameters); + + if ( mp_hash == ON_MeshParameters::DefaultMesh.GeometrySettingsHash(bIgnoreSubDParameters)) return ON_MeshParameters::Type::Default; - if ( mp_hash == ON_MeshParameters::FastRenderMesh.GeometrySettingsHash()) + if ( mp_hash == ON_MeshParameters::FastRenderMesh.GeometrySettingsHash(bIgnoreSubDParameters)) return ON_MeshParameters::Type::FastRender; - if ( mp_hash == ON_MeshParameters::QualityRenderMesh.GeometrySettingsHash()) + if ( mp_hash == ON_MeshParameters::QualityRenderMesh.GeometrySettingsHash(bIgnoreSubDParameters)) return ON_MeshParameters::Type::QualityRender; - if ( mp_hash == ON_MeshParameters::DefaultAnalysisMesh.GeometrySettingsHash()) + if ( mp_hash == ON_MeshParameters::DefaultAnalysisMesh.GeometrySettingsHash(bIgnoreSubDParameters)) return ON_MeshParameters::Type::DefaultAnalysis; const double mesh_density = MeshDensity(); @@ -11544,13 +11588,13 @@ void ON_MappingTag::Dump( ON_TextLog& text_log ) const text_log.Print("box"); break; case ON_TextureMapping::TYPE::mesh_mapping_primitive: - text_log.Print("mesh primative"); + text_log.Print("mesh primitive"); break; case ON_TextureMapping::TYPE::srf_mapping_primitive: - text_log.Print("srf primative"); + text_log.Print("srf primitive"); break; case ON_TextureMapping::TYPE::brep_mapping_primitive: - text_log.Print("brep primative"); + text_log.Print("brep primitive"); break; case ON_TextureMapping::TYPE::ocs_mapping: text_log.Print("ocs"); @@ -13193,7 +13237,7 @@ static int Internal_FaceDegenerateAreaCheck( else if (a[1] < a[0] ) { a[0] = ON_CrossProduct(V[2] - V[1], V[3] - V[1]).Length(); - a[1] = ON_CrossProduct(V[3] - V[1], V[2] - V[1]).Length(); + a[1] = ON_CrossProduct(V[3] - V[1], V[0] - V[1]).Length(); if (a[0] > atol) { if (a[1] > atol) diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h index bc872948..31121f5d 100644 --- a/opennurbs_mesh.h +++ b/opennurbs_mesh.h @@ -661,7 +661,7 @@ public: /* Returns: - The type of geometry settings. + The type of geometry settings taking SubD parameters into account. Remarks: This function will never return ON_MeshParameters::Type::Unset. In particular, if the return value is not ON_MeshParameters::Type::Custom, @@ -669,6 +669,21 @@ public: */ ON_MeshParameters::Type GeometrySettingsType() const; + /* + Returns: + The type of geometry settings. + Parameters: + bIgnoreSubDParameters - [in] + If true, SubD meshing parameters are ignored in determining the type. + Remarks: + This function will never return ON_MeshParameters::Type::Unset. + In particular, if the return value is not ON_MeshParameters::Type::Custom, + then the settings come from one of the built-in mesh creation settings. + */ + ON_MeshParameters::Type GeometrySettingsType( + bool bIgnoreSubDParameters + ) const; + /* Description: Mesh creationg parameters to create the default render mesh. @@ -730,7 +745,7 @@ public: /* Returns: - If these mesh parameters were created from ON_MeshParameters::CreateFromMeshDensity(normalized_mesh_density), + If these mesh parameters, including the SubD meshing parameters, were created from ON_MeshParameters::CreateFromMeshDensity(normalized_mesh_density), then normalized_mesh_density is returned. Otherwise, ON_DBL_QNAN is returned. Remarks: @@ -740,6 +755,21 @@ public: */ double MeshDensity() const; + /* + Parameters: + bIgnoreSubDParameters - [in] + If true, SubD meshing parameters are ignored. + Returns: + If these mesh parameters were created from ON_MeshParameters::CreateFromMeshDensity(normalized_mesh_density), + then normalized_mesh_density is returned. + Otherwise, ON_DBL_QNAN is returned. + Remarks: + The values of m_bDoublePrecision, m_bClosedObjectPostProcess, and m_texture_range can be arbitrary + because they do not determine geometry of the resulting mesh and are typically ingored. + You must compare these properties if they matter in your particular context. + */ + double MeshDensity(bool bIgnoreSubDParameters ) const; + /* Description: Convert a mesh density value to a percentage with finite precision fuzz removed. @@ -873,6 +903,22 @@ public: */ ON_SHA1_Hash GeometrySettingsHash() const; + /* + Parameters: + bIgnoreSubDParameters - [in] + If true, the SubD meshing parameters are not hashed. + Returns: + A hash of values that control mesh geometry. + Remarks: + Teh has intentionally ignores + m_bCustomSettings, m_bCustomSettingsEnabled, m_bComputeCurvature, + m_bDoublePrecision, m_bClosedObjectPostProcess, m_texture_range. + If you need to include those values, call ContentHash(). + */ + ON_SHA1_Hash GeometrySettingsHash( + bool bIgnoreSubDParameters + ) const; + ON_UUID MesherId() const; void SetMesherId( ON_UUID @@ -1226,7 +1272,7 @@ private: int m_grid_min_count = 0; int m_grid_max_count = 0; - mutable ON_SHA1_Hash m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest; + mutable ON_SHA1_Hash m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest; // the cached hash includes SubD meshing parameters ON__UINT32 m_reserved2 = 0; diff --git a/opennurbs_point.cpp b/opennurbs_point.cpp index a3a31249..70f0234d 100644 --- a/opennurbs_point.cpp +++ b/opennurbs_point.cpp @@ -1096,6 +1096,14 @@ bool ON_3fVector::PerpendicularTo( const ON_3fVector& v ) return V.IsPerpendicularTo(ON_3dVector(v)); } +const ON_3dVector ON_3dVector::Perpendicular( + ON_3dVector failure_result +) const +{ + ON_3dVector perpendicular; + return perpendicular.PerpendicularTo(*this) ? perpendicular : failure_result; +} + bool ON_3dVector::PerpendicularTo( const ON_3dVector& v ) { //bool rc = false; @@ -6576,6 +6584,23 @@ ON_3dVector ON_CrossProduct( const ON_3dVector& a , const ON_3dVector& b ) return ON_3dVector(a.y*b.z - b.y*a.z, a.z*b.x - b.z*a.x, a.x*b.y - b.x*a.y ); } +double ON_3dVector::DotProduct( + ON_3dVector A, + ON_3dVector B +) +{ + return (A.x * B.x + A.y * B.y + A.z * B.z); +} + +const ON_3dVector ON_3dVector::CrossProduct( + ON_3dVector A, + ON_3dVector B +) +{ + return ON_3dVector(A.y * B.z - B.y * A.z, A.z * B.x - B.z * A.x, A.x * B.y - B.x * A.y); +} + + ON_3dVector ON_CrossProduct( const double* a, const double* b ) { return ON_3dVector(a[1]*b[2] - b[1]*a[2], a[2]*b[0] - b[2]*a[0], a[0]*b[1] - b[0]*a[1] ); @@ -6713,6 +6738,44 @@ bool ON_PlaneEquation::Create( ON_3dPoint P, ON_3dVector N ) return rc; } +const ON_PlaneEquation ON_PlaneEquation::CreateFromThreePoints( + ON_3dPoint pointA, + ON_3dPoint pointB, + ON_3dPoint pointC +) +{ + if (pointA.IsValid() && pointB.IsValid() && pointC.IsValid()) + { + const ON_3dVector X = pointB - pointA; + const ON_3dVector Y = pointC - pointA; + return ON_PlaneEquation::CreateFromPointAndNormal(pointA, ON_3dVector::CrossProduct(X, Y)); + } + return ON_PlaneEquation::NanPlaneEquation; +} + +const ON_PlaneEquation ON_PlaneEquation::CreateFromPointAndNormal( + ON_3dPoint point, + ON_3dVector normal +) +{ + if (point.IsValid() && normal.IsValid()) + { + const ON_3dVector N = normal.UnitVector(); + if (false == normal.IsUnitVector() || fabs(1.0 - N.Length()) < fabs(1.0 - normal.Length()) * (1.0 - ON_ZERO_TOLERANCE)) + normal = N; // N is a better unit vector + if (normal.IsUnitVector()) + { + ON_PlaneEquation e; + e.x = normal.x; + e.y = normal.y; + e.z = normal.z; + e.d = -(e.x * point.x + e.y * point.y + e.z * point.z); + return e; + } + } + return ON_PlaneEquation::NanPlaneEquation; +} + ON_PlaneEquation::ON_PlaneEquation() : x(0.0) , y(0.0) @@ -6912,6 +6975,11 @@ bool ON_PlaneEquation::IsSet() const ); } +bool ON_PlaneEquation::IsUnitized() const +{ + return (IsSet() && ON_3dVector(x, y, z).IsUnitVector()) ? true : false; +} + ON_3dVector ON_PlaneEquation::Direction() const { return ON_3dVector(x, y, z); @@ -7677,6 +7745,13 @@ bool ON_PlaneEquation::operator!=( const ON_PlaneEquation& eq ) const return (x!=eq.x || y!=eq.y || z!=eq.z || d!=eq.d)?true:false; } +const ON_PlaneEquation operator*(const ON_Xform& xform, const ON_PlaneEquation& e) +{ + ON_PlaneEquation xe(e); + xe.Transform(xform); + return xe; +} + // Find the maximum absolute value of a array of (possibly homogeneous) points double ON_MaximumCoordinate(const double* data, int dim, bool is_rat, int count) { diff --git a/opennurbs_point.h b/opennurbs_point.h index b02557ff..22b726ce 100644 --- a/opennurbs_point.h +++ b/opennurbs_point.h @@ -1398,6 +1398,16 @@ public: double& operator[](unsigned int); double operator[](unsigned int) const; + static double DotProduct( + ON_3dVector A, + ON_3dVector B + ); + + static const ON_3dVector CrossProduct( + ON_3dVector A, + ON_3dVector B + ); + /* Returns: False if any coordinate is infinte, a nan, or ON_UNSET_VALUE. @@ -1534,6 +1544,20 @@ public: const ON_3dPoint&, const ON_3dPoint&, const ON_3dPoint& ); + /* + Parameters: + failure_result - [in] + vector to return if this is zero or unset. + When in doubt, pass ON_3dVector::NanVector. + Returns: + If this is nonzero, a vector perpindicular to this. + The returned vector is not unitized. + Otherwise failure_result is returned. + */ + const ON_3dVector Perpendicular( + ON_3dVector failure_result + ) const; + // These transform the vector in place. The transformation matrix acts on // the left of the vector; i.e., result = transformation*vector void Transform( @@ -1576,6 +1600,31 @@ public: static const ON_PlaneEquation ZeroPlaneEquation; // (0.0,0.0,0.0,0.0) static const ON_PlaneEquation NanPlaneEquation; // (ON_DBL_QNAN,ON_DBL_QNAN,ON_DBL_QNAN,ON_DBL_QNAN) + static const ON_PlaneEquation WorldXY; // (0,0,1,0) + static const ON_PlaneEquation WorldYZ; // (1,0,0,0) + static const ON_PlaneEquation WorldZX; // (0,1,0,0) + + /* + Returns: + If the three points are valid and not colinear, a unitized plane equation is returned. + Otherwise ON_PlaneEquation::NanPlaneEquation is returned. + */ + static const ON_PlaneEquation CreateFromThreePoints( + ON_3dPoint pointA, + ON_3dPoint pointB, + ON_3dPoint pointC + ); + + /* + Returns: + If point is valid and normal is nonzero, a unitized plane equation is returned. + Otherwise ON_PlaneEquation::NanPlaneEquation is returned. + */ + static const ON_PlaneEquation CreateFromPointAndNormal( + ON_3dPoint point, + ON_3dVector normal + ); + ON_PlaneEquation(); ON_PlaneEquation( @@ -1620,11 +1669,16 @@ public: /* Description: - returns true if x, y, z, d are valid, finite doubles and - at least one of x, y or z is not zero. + returns true if x, y, z, d are valid, finite doubles and at least one of x, y or z is not zero. */ bool IsSet() const; + /* + Description: + returns true if x, y, z, d are valid, finite doubles and x^2 + y^2 + z^2 = 1. + */ + bool IsUnitized() const; + /* Description: Sets (x,y,z) to a unitized N and then sets @@ -1972,6 +2026,10 @@ public: void Dump(class ON_TextLog&) const; }; +ON_DECL +const ON_PlaneEquation operator*(const ON_Xform&, const ON_PlaneEquation&); + + #if defined(ON_DLL_TEMPLATE) ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; @@ -2245,6 +2303,16 @@ public: // 3d bounding box of point list. ON_BoundingBox BoundingBox() const; + // Description: + // Get 3d axis aligned bounding box + // of a subset of the points. + // Parameters: + // from - [in] start of the box calculation + // count - [in] number of items computed + // Returns: + // 3d bounding box of point list. + ON_BoundingBox BoundingBox(int from, int count) const; + // Description: // Get 3d axis aligned bounding box or the union // of the input box with the point list's bounding box. diff --git a/opennurbs_pointcloud.cpp b/opennurbs_pointcloud.cpp index bb418f05..1077109d 100644 --- a/opennurbs_pointcloud.cpp +++ b/opennurbs_pointcloud.cpp @@ -219,6 +219,29 @@ ON::object_type ON_PointCloud::ObjectType() const return ON::pointset_object; } +unsigned int ON_PointCloud::SizeOf() const +{ + // Dale Lear fixed this function in April 2021. + // Before that all point clouds were reported to have + // ON_Geometry::SizeOf() bytes. + + size_t sz = ON_Geometry::SizeOf(); + + sz += (sizeof(*this) - sizeof(ON_Geometry)); + sz += (size_t)(m_P.SizeOfArray()); + sz += (size_t)(m_N.SizeOfArray()); + sz += (size_t)(m_C.SizeOfArray()); + sz += (size_t)(m_V.SizeOfArray()); + sz += (size_t)(m_H.SizeOfArray()); + + // Avoid overflowing 4 byte unsigned int + // and hope the consumer of this information is + // converting back to size_t before doing arithmetic. + // The low 4 bytes are zeroed so small additions won't + // overflow unsigned int. + return (sz > 0xFFFF0000U) ? 0xFFFF0000U : ((unsigned int)sz); +} + int ON_PointCloud::Dimension() const { @@ -550,3 +573,113 @@ bool ON_PointCloud::PointIsHidden( int point_index ) const : false; } +ON_PointCloud* ON_PointCloud::RandomSubsample( + const ON_PointCloud* source_point_cloud, + const unsigned int subsample_point_count, + ON_PointCloud* destination_point_cloud, + ON_ProgressReporter* progress_reporter, + ON_Terminator* terminator +) +{ + if ( + nullptr == source_point_cloud + || subsample_point_count <= 0 + || subsample_point_count >= source_point_cloud->m_P.UnsignedCount() + ) + return nullptr; + + const unsigned int point_count = source_point_cloud->m_P.UnsignedCount(); + const unsigned int points_to_remove = point_count - subsample_point_count; + if (points_to_remove <= 0) + return nullptr; + + const bool bHaveNormals = point_count == source_point_cloud->m_N.UnsignedCount(); + const bool bHaveColors = point_count == source_point_cloud->m_C.UnsignedCount(); + const bool bHaveValues = point_count == source_point_cloud->m_V.UnsignedCount(); + const bool bHaveHidden = point_count == source_point_cloud->m_H.UnsignedCount(); + + bool bAlloc = false; + if (destination_point_cloud) + { + if (source_point_cloud != destination_point_cloud) + { + destination_point_cloud->Destroy(); + *destination_point_cloud = *source_point_cloud; + } + } + else + { + destination_point_cloud = new ON_PointCloud(*source_point_cloud); + bAlloc = true; + } + + ON_RandomNumberGenerator gen; + gen.Seed(); + + unsigned int last_point_count = point_count; + for (unsigned int i = 0; i < points_to_remove; i++) + { + if (terminator && ON_Terminator::TerminationRequested(terminator)) + { + if (source_point_cloud != destination_point_cloud) + destination_point_cloud->Destroy(); + if (bAlloc) + delete destination_point_cloud; + return nullptr; + } + + if (progress_reporter) + ON_ProgressReporter::ReportProgress(progress_reporter, i / (double)points_to_remove); + + // For (min <= r < max): min + RandomNumber() % (max-min) + const int point_index = gen.RandomNumber() % last_point_count; + + destination_point_cloud->m_P.Swap(point_index, last_point_count); + if (bHaveNormals) + destination_point_cloud->m_N.Swap(point_index, last_point_count); + if (bHaveColors) + destination_point_cloud->m_C.Swap(point_index, last_point_count); + if (bHaveValues) + destination_point_cloud->m_V.Swap(point_index, last_point_count); + if (bHaveHidden) + destination_point_cloud->m_H.Swap(point_index, last_point_count); + + last_point_count--; + if (last_point_count <= 0) + break; + } + + if (progress_reporter) + ON_ProgressReporter::ReportProgress(progress_reporter, 1.0); + + destination_point_cloud->m_P.SetCount(subsample_point_count); + destination_point_cloud->m_P.Shrink(); + if (bHaveNormals) + { + destination_point_cloud->m_N.SetCount(subsample_point_count); + destination_point_cloud->m_N.Shrink(); + } + if (bHaveColors) + { + destination_point_cloud->m_C.SetCount(subsample_point_count); + destination_point_cloud->m_C.Shrink(); + } + if (bHaveValues) + { + destination_point_cloud->m_V.SetCount(subsample_point_count); + destination_point_cloud->m_V.Shrink(); + } + if (bHaveHidden) + { + destination_point_cloud->m_H.SetCount(subsample_point_count); + destination_point_cloud->m_H.Shrink(); + destination_point_cloud->m_hidden_count = 0; + for (unsigned int i = 0; i < destination_point_cloud->m_H.UnsignedCount(); i++) + { + if (destination_point_cloud->m_H[i]) + destination_point_cloud->m_hidden_count++; + } + } + + return destination_point_cloud; +} diff --git a/opennurbs_pointcloud.h b/opennurbs_pointcloud.h index 50251c43..eba6b47e 100644 --- a/opennurbs_pointcloud.h +++ b/opennurbs_pointcloud.h @@ -78,6 +78,9 @@ public: // virtual ON_Object override ON::object_type ObjectType() const override; + // virtual ON_Object::SizeOf override + unsigned int SizeOf() const override; + // virtual ON_Geometry override int Dimension() const override; @@ -205,6 +208,30 @@ public: */ bool PointIsHidden( int point_index ) const; + /* + Description: + Returns a random subsample of a point cloud. + Parameters: + source_point_cloud - [in] The point cloud to subsample. + subsample_point_count - [in] The number of points to keep. + destination_point_cloud - [out] If nullptr, then a new ON_PointCloud() + will be the destination. This can be the source point cloud. + progress_reporter - [in] Optional progress reporter, can be nullptr. + terminator - [in] Optional terminator, can be nullptr. + Returns: + A new ON_PointCloud which is a subsample of source_point_cloud. + If destination_point_cloud == nullptr, then memory will be allocated for + the returned point cloud and becomes the responsibility of the caller. + */ + static ON_PointCloud* RandomSubsample( + const ON_PointCloud* source_point_cloud, + const unsigned int subsample_point_count, + ON_PointCloud* destination_point_cloud, + ON_ProgressReporter* progress_reporter, + ON_Terminator* terminator + ); + + ///////////////////////////////////////////////////////////////// // Implementation ON_3dPointArray m_P; diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 13281484..c365e1e0 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,18 +6,18 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 7 -#define RMA_VERSION_MINOR 1 +#define RMA_VERSION_MINOR 6 //////////////////////////////////////////////////////////////// // // These are set automatically by the build system as the // first step in each build. // -#define RMA_VERSION_YEAR 2020 -#define RMA_VERSION_MONTH 12 -#define RMA_VERSION_DATE 8 -#define RMA_VERSION_HOUR 9 -#define RMA_VERSION_MINUTE 49 +#define RMA_VERSION_YEAR 2021 +#define RMA_VERSION_MONTH 5 +#define RMA_VERSION_DATE 7 +#define RMA_VERSION_HOUR 19 +#define RMA_VERSION_MINUTE 0 //////////////////////////////////////////////////////////////// // @@ -35,20 +35,20 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 7,1,20343,9490 -#define VERSION_WITH_PERIODS 7.1.20343.09490 -#define COPYRIGHT "Copyright (C) 1993-2020, Robert McNeel & Associates. All Rights Reserved." +#define VERSION_WITH_COMMAS 7,6,21127,19000 +#define VERSION_WITH_PERIODS 7.6.21127.19000 +#define COPYRIGHT "Copyright (C) 1993-2021, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." #define RMA_VERSION_NUMBER_MAJOR_STRING "7" #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"7" #define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"6" -#define RMA_VERSION_NUMBER_SR_STRING "SR1" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR1" +#define RMA_VERSION_NUMBER_SR_STRING "SR6" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR6" -#define RMA_VERSION_WITH_PERIODS_STRING "7.1.20343.09490" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.1.20343.09490" +#define RMA_VERSION_WITH_PERIODS_STRING "7.6.21127.19000" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.6.21127.19000" diff --git a/opennurbs_rand.cpp b/opennurbs_rand.cpp index ac358282..4f9465bd 100644 --- a/opennurbs_rand.cpp +++ b/opennurbs_rand.cpp @@ -304,6 +304,28 @@ double ON_RandomNumberGenerator::RandomDouble(const class ON_Interval& range) return RandomDouble(range.m_t[0], range.m_t[1]); } +int ON_RandomNumberGenerator::RandomSignedInteger(int i0, int i1) +{ + const ON__UINT32 r = RandomNumber(); + const ON__UINT32 delta = (i0 < i1) ? ((unsigned)(i1 - i0)) : ((unsigned)(i0 - i1)); + return + (0xFFFFFFFFU == delta) + ? ((int)r) // avoid delta+1 overflow and crash + : (((i0 < i1) ? i0 : i1) + ((int)(r % (delta + 1U)))) + ; +} + +unsigned int ON_RandomNumberGenerator::RandomUnsignedInteger(unsigned int i0, unsigned int i1) +{ + const ON__UINT32 r = RandomNumber(); + const ON__UINT32 delta = (i0 < i1) ? (i1 - i0) : (i0 - i1); + return + (0xFFFFFFFFU == delta) + ? ((int)r) // avoid delta+1 overflow and crash + : (((i0 < i1) ? i0 : i1) + (r % (delta + 1U))) + ; +} + static void Swap1(size_t count, unsigned char* a, unsigned char* b) { unsigned char t; diff --git a/opennurbs_rand.h b/opennurbs_rand.h index 2bda1388..72ab5b10 100644 --- a/opennurbs_rand.h +++ b/opennurbs_rand.h @@ -152,6 +152,28 @@ public: double RandomDouble(const class ON_Interval& range); + /* + Parameters: + i0 - [in] + i1 - [in] + Returns: + A signed integer in the interval [min(i0,i1), max(i0,i1)], inclusive + Example + RandomSignedInteger(-2,3) will return -2, -1, 0, 1, 2, or 3. + */ + int RandomSignedInteger(int i0, int i1); + + /* + Parameters: + i0 - [in] + i1 - [in] + Returns: + An unsigned integer in the interval [min(i0,i1), max(i0,i1)], inclusive + Example + RandomUnsignedInteger(3,7) will return 3, 4, 5, 6, or 7. + */ + unsigned int RandomUnsignedInteger(unsigned int i0, unsigned int i1); + /* Description: Perform a random permuation on an array. diff --git a/opennurbs_revsurface.cpp b/opennurbs_revsurface.cpp index 6593dbde..98d13ad5 100644 --- a/opennurbs_revsurface.cpp +++ b/opennurbs_revsurface.cpp @@ -1535,17 +1535,18 @@ bool ON_RevSurface::GetBBox( // returns true if successful abox.Set(P,false); while( m_axis.ClosestPointTo(P,&t) ) // not a loop - used for flow control { - abox.Set(arc.plane.origin,true); // If we cannot construct a valid arc, then P and the point on the axis // are added to the bounding box. One case where this happens is when // P is on the axis of revolution. See bug 84354. arc.plane.origin = m_axis.PointAt(t); arc.plane.xaxis = P-arc.plane.origin; + abox.Set(arc.plane.origin,true); arc.radius = arc.plane.xaxis.Length(); if ( !arc.plane.xaxis.Unitize() ) break; - if ( fabs(arc.plane.xaxis*arc.plane.zaxis) > 0.0001 ) + double dot = arc.plane.xaxis*arc.plane.zaxis; + if ( fabs(dot) > 0.0001 ) break; arc.plane.yaxis = ON_CrossProduct(arc.plane.zaxis,arc.plane.xaxis); if ( !arc.plane.yaxis.Unitize() ) diff --git a/opennurbs_rtree.cpp b/opennurbs_rtree.cpp index 3ec14c38..412b6db9 100644 --- a/opennurbs_rtree.cpp +++ b/opennurbs_rtree.cpp @@ -71,6 +71,8 @@ static void InitRect(ON_RTreeBBox* a_rect); static ON_RTreeBBox CombineRectHelper(const ON_RTreeBBox* a_rectA, const ON_RTreeBBox* a_rectB); static double CalcRectVolumeHelper(const ON_RTreeBBox* a_rect); static bool OverlapHelper(const ON_RTreeBBox* a_rectA, const ON_RTreeBBox* a_rectB); +static bool OverlapLineHelper(const ON_Line* a_line, const ON_RTreeBBox* a_rectB); +static bool OverlapInfiniteLineHelper(const ON_Line* a_line, const ON_RTreeBBox* a_rectB); static double DistanceToCapsuleAxisHelper(const struct ON_RTreeCapsule* a_capsule, const ON_RTreeBBox* a_rect); static void ClassifyHelper(int a_index, int a_group, struct ON_RTreePartitionVars* a_parVars); static bool SearchHelper(const ON_RTreeNode* a_node, ON_RTreeBBox* a_rect, ON_RTreeSearchResultCallback& a_result ); @@ -80,6 +82,8 @@ static bool SearchHelper(const ON_RTreeNode* a_node, const ON_RTreeBBox* a_rect, static bool SearchHelper(const ON_RTreeNode* a_node, const ON_RTreeBBox* a_rect, ON_SimpleArray &a_result ); static bool SearchHelper(const ON_RTreeNode* a_node, struct ON_RTreeSphere* a_sphere, ON_RTreeSearchResultCallback& a_result ); static bool SearchHelper(const ON_RTreeNode* a_node, struct ON_RTreeCapsule* a_capsule, ON_RTreeSearchResultCallback& a_result ); +static bool SearchHelper(const ON_RTreeNode* a_node, const class ON_Line* a_line, ON_RTreeSearchResultCallback& a_result); +static bool SearchInfiniteLineHelper(const ON_RTreeNode* a_node, const class ON_Line* a_line, ON_RTreeSearchResultCallback& a_result); //////////////////////////////////////////////////////////////// // @@ -668,6 +672,152 @@ bool ON_RTree::CreateMeshFaceTree( const ON_Mesh* mesh ) return (0 != m_root); } +bool ON_SubDRTree::CreateSubDVertexRTree( + const ON_SubD& subd, + ON_SubDComponentLocation vertex_location +) +{ + // ShareContentsFrom() increments the reference count on m_subdimple_sp + // so vertex pointers one this RTree's nodes will be valid for the duration + // of the rtree's existence. + m_subd.ShareContentsFrom(const_cast(subd)); + + this->RemoveAll(); + + ON_SubDVertexIterator vit(m_subd); + + for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex()) + { + const ON_3dPoint P = v->Point(vertex_location); + if (false == this->Insert(&P.x, &P.x, (void*)v)) + { + this->RemoveAll(); + return false; + } + } + return (nullptr != this->Root()); +} + +const ON_SubDVertex* ON_SubDRTree::FindVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance +) const +{ + ON_SubDRTreeVertexFinder vf; + vf = ON_SubDRTreeVertexFinder::Create(P); + + ON_BoundingBox rbox; + const ON_3dVector rtol(distance_tolerance, distance_tolerance, distance_tolerance); + + rbox.m_min = vf.m_P - rtol; + rbox.m_max = vf.m_P + rtol; + // vtree.Search() can return true (found nearby) and false (found exact) because + // ON_SubDRTreeVertexFinder::Callback() cancels the search when a vertex at the exact location + // is found. + this->Search(&rbox.m_min.x, &rbox.m_max.x, ON_SubDRTreeVertexFinder::Callback, &vf); + return vf.m_v; +} + + +const ON_SubDVertex* ON_SubDRTree::FindMarkedVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance +) const +{ + ON_SubDRTreeVertexFinder vf; + vf = ON_SubDRTreeVertexFinder::Create(P, false); + + ON_BoundingBox rbox; + const ON_3dVector rtol(distance_tolerance, distance_tolerance, distance_tolerance); + + rbox.m_min = vf.m_P - rtol; + rbox.m_max = vf.m_P + rtol; + // vtree.Search() can return true (found nearby) and false (found exact) because + // ON_SubDRTreeVertexFinder::Callback() cancels the search when a vertex at the exact location + // is found. + this->Search(&rbox.m_min.x, &rbox.m_max.x, ON_SubDRTreeVertexFinder::Callback, &vf); + return vf.m_v; +} + + +const ON_SubDVertex* ON_SubDRTree::FindUnmarkedVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance +) const +{ + ON_SubDRTreeVertexFinder vf; + vf = ON_SubDRTreeVertexFinder::Create(P, true); + + ON_BoundingBox rbox; + const ON_3dVector rtol(distance_tolerance, distance_tolerance, distance_tolerance); + + rbox.m_min = vf.m_P - rtol; + rbox.m_max = vf.m_P + rtol; + // vtree.Search() can return true (found nearby) and false (found exact) because + // ON_SubDRTreeVertexFinder::Callback() cancels the search when a vertex at the exact location + // is found. + this->Search(&rbox.m_min.x, &rbox.m_max.x, ON_SubDRTreeVertexFinder::Callback, &vf); + return vf.m_v; +} + +void ON_SubDRTree::Clear() +{ + RemoveAll(); // clear the rtree + m_subd = ON_SubD::Empty; // clear an references to a subdimple. +} + +const ON_SubD& ON_SubDRTree::SubD() const +{ + return m_subd; +} + +const ON_SubDRTreeVertexFinder ON_SubDRTreeVertexFinder::Create(const ON_3dPoint P) +{ + ON_SubDRTreeVertexFinder vf; + vf.m_P = P; + return vf; +} + +const ON_SubDRTreeVertexFinder ON_SubDRTreeVertexFinder::Create(const ON_3dPoint P, bool bMarkFilter) +{ + ON_SubDRTreeVertexFinder vf = ON_SubDRTreeVertexFinder::Create(P); + vf.m_bMarkFilterEnabled = true; + vf.m_bMarkFilter = bMarkFilter; + return vf; +} + +bool ON_SubDRTreeVertexFinder::Callback(void* a_context, ON__INT_PTR a_id) +{ + for (;;) + { + ON_SubDRTreeVertexFinder* vf = (ON_SubDRTreeVertexFinder*)a_context; + const ON_SubDVertex* v = (const ON_SubDVertex*)a_id; + if (nullptr == v || (vf->m_bMarkFilterEnabled && vf->m_bMarkFilter != v->Mark())) + break; // when m_bMarkFilterEnabled is true, only vertices with v->Mark() == m_bMarkFilter can be found. + const double d = (vf->m_P - v->ControlNetPoint()).MaximumCoordinate(); + if (d >= 0.0) + { + if (nullptr == vf->m_v) + { + vf->m_v = v; + vf->m_distance = d; + } + else + { + if (d < vf->m_distance || (d == vf->m_distance && v->m_id < vf->m_v->m_id)) + { + vf->m_v = v; + vf->m_distance = d; + } + } + if (0.0 == d) + return false; // can't get any closer. Stop searching. + } + break; + } + return true; +} + bool ON_RTree::Insert2d(const double a_min[2], const double a_max[2], int a_element_id) { const double min3d[3] = {a_min[0],a_min[1],0.0}; @@ -863,6 +1013,129 @@ bool ON_RTree::Search( return SearchHelper(m_root, a_rect, result); } +bool ON_RTree::Search( + const ON_Line* a_line, + bool ON_CALLBACK_CDECL a_resultCallback(void* a_context, ON__INT_PTR a_data), + void* a_context +) const +{ + return ON_RTree::Search(a_line, false, a_resultCallback, a_context); +} + +bool ON_RTree::Search( + const ON_Line* a_line, + bool infinite, + bool ON_CALLBACK_CDECL a_resultCallback(void* a_context, ON__INT_PTR a_data), + void* a_context +) const +{ + if (0 == m_root || 0 == a_line) + return false; + + ON_RTreeSearchResultCallback result; + result.m_context = a_context; + result.m_resultCallback = a_resultCallback; + if (infinite) + return SearchInfiniteLineHelper(m_root, a_line, result); + else + return SearchHelper(m_root, a_line, result); +} + +class ON_RTreeSearchPolylineResultCallback : public ON_RTreeSearchResultCallback +{ +public: + ON_Workspace* m_ws; +}; + +static bool SearchPolylinePart(const ON_RTreeNode* a_node, const ON_Polyline* polyline, int from, int plcount, + ON_RTreeSearchPolylineResultCallback& result) +{ + if (plcount > 2) + { + int i, count, innercount; + + if ((count = a_node->m_count) > 0) + { + const ON_RTreeBranch* branch = a_node->m_branch; + if (a_node->IsInternalNode()) + { + innercount = (plcount + 1) / 2; + auto bb = (ON_BoundingBox*)result.m_ws->GetMemory(sizeof(ON_BoundingBox) * 2); + *bb = polyline->BoundingBox(from, innercount); + *(bb + 1) = polyline->BoundingBox(from + innercount - 1, plcount - innercount + 1); + + for (i = 0; i < count; ++i) + { + if (OverlapHelper((ON_RTreeBBox*)bb, &branch[i].m_rect)) + { + if (!SearchPolylinePart(branch[i].m_child, polyline, from, innercount, result)) + { + return false; // Don't continue searching + } + } + if (OverlapHelper((ON_RTreeBBox*)bb+1, &branch[i].m_rect)) + { + if (!SearchPolylinePart(branch[i].m_child, polyline, from + innercount - 1, plcount - innercount + 1, result)) + { + return false; // Don't continue searching + } + } + } + } + else + { + // a_node is a leaf node + for (i = 0; i < count; ++i) + { + for (innercount = 0; innercount < (plcount-1); innercount++) + { + ON_Line* line = (ON_Line*)(&polyline->Array()[innercount + from]); + if (OverlapLineHelper(line, &branch[i].m_rect)) + { + if (result.m_context) ((ON_RTreePolylineContext*)result.m_context)->m_polyline_pointindex = innercount + from; + if (!result.m_resultCallback(result.m_context, branch[i].m_id)) + { + // callback canceled search + return false; + } + } + } + } + } + } + } + else if (plcount == 2) + { + ON_Line* line = (ON_Line*)(&polyline->Array()[from]); + if (result.m_context) ((ON_RTreePolylineContext*)result.m_context)->m_polyline_pointindex = from; + return SearchHelper(a_node, line, result); + } + else if (plcount < 2) + { + ON_ERROR("Unexpected plcount"); + return true; + } + + return true; +} + +bool ON_RTree::Search( + const ON_Polyline* polyline, + bool ON_CALLBACK_CDECL resultCallback(void* a_context, ON__INT_PTR a_id), + ON_RTreePolylineContext* a_context +) const +{ + if (0 == m_root || 0 == polyline || nullptr == resultCallback) return false; + if (polyline->UnsignedCount() < 2) return true; + + ON_RTreeSearchPolylineResultCallback result; + result.m_context = a_context; + result.m_resultCallback = resultCallback; + ON_Workspace ws; + result.m_ws = &ws; + return SearchPolylinePart(m_root, polyline, 0, polyline->Count(), result); +} + bool ON_RTree::Search( struct ON_RTreeSphere* a_sphere, bool ON_CALLBACK_CDECL a_resultCallback(void* a_context, ON__INT_PTR a_id), @@ -3021,6 +3294,21 @@ bool OverlapHelper(const ON_RTreeBBox* a_rectA, const ON_RTreeBBox* a_rectB) return true; } +// Decide whether a box and a line overlap. +bool OverlapLineHelper(const ON_Line* line, const ON_RTreeBBox* rect) +{ + ON_BoundingBox* bbox = (ON_BoundingBox*)rect; + return !bbox->IsDisjoint(*line, false); +} + +// Decide whether a box and an infinite line overlap. +bool OverlapInfiniteLineHelper(const ON_Line* line, const ON_RTreeBBox* rect) +{ + ON_BoundingBox* bbox = (ON_BoundingBox*)rect; + return !bbox->IsDisjoint(*line, true); +} + + //static bool OverlapHelper(const struct ON_RTreeSphere* a_sphere, const ON_RTreeBBox* a_rect) //{ // double d[3], t, r; @@ -3258,6 +3546,8 @@ static double DistanceToCapsuleAxisHelper(const struct ON_RTreeCapsule* a_capsul return ((const ON_BoundingBox*)a_rect->m_min)->MinimumDistanceTo( *((const ON_Line*)L[0]) ); } + + // Add a node to the reinsertion list. All its branches will later // be reinserted into the index structure. @@ -3741,6 +4031,96 @@ bool SearchHelper(const ON_RTreeNode* a_node, struct ON_RTreeCapsule* a_capsule, return true; // Continue searching } +static +bool SearchInfiniteLineHelper(const ON_RTreeNode* a_node, const ON_Line* a_line, ON_RTreeSearchResultCallback& a_result) +{ + // NOTE: + // Some versions of ON_RTree::Search shrink a_line as the search progresses. + int i, count; + + if ((count = a_node->m_count) > 0) + { + const ON_RTreeBranch* branch = a_node->m_branch; + if (a_node->IsInternalNode()) + { + // a_node is an internal node - search m_branch[].m_child as needed + for (i = 0; i < count; ++i) + { + if (OverlapInfiniteLineHelper(a_line, &branch[i].m_rect)) + { + if (!SearchInfiniteLineHelper(branch[i].m_child, a_line, a_result)) + { + return false; // Don't continue searching + } + } + } + } + else + { + // a_node is a leaf node - return m_branch[].m_id values + for (i = 0; i < count; ++i) + { + if (OverlapInfiniteLineHelper(a_line, &branch[i].m_rect)) + { + if (!a_result.m_resultCallback(a_result.m_context, branch[i].m_id)) + { + // callback canceled search + return false; + } + } + } + } + } + + return true; // Continue searching +} + + +static +bool SearchHelper(const ON_RTreeNode* a_node, const ON_Line* a_line, ON_RTreeSearchResultCallback& a_result) +{ + // NOTE: + // Some versions of ON_RTree::Search shrink a_rect as the search progresses. + int i, count; + + if ((count = a_node->m_count) > 0) + { + const ON_RTreeBranch* branch = a_node->m_branch; + if (a_node->IsInternalNode()) + { + // a_node is an internal node - search m_branch[].m_child as needed + for (i = 0; i < count; ++i) + { + if (OverlapLineHelper(a_line, &branch[i].m_rect)) + { + if (!SearchHelper(branch[i].m_child, a_line, a_result)) + { + return false; // Don't continue searching + } + } + } + } + else + { + // a_node is a leaf node - return m_branch[].m_id values + for (i = 0; i < count; ++i) + { + if (OverlapLineHelper(a_line, &branch[i].m_rect)) + { + if (!a_result.m_resultCallback(a_result.m_context, branch[i].m_id)) + { + // callback canceled search + return false; + } + } + } + } + } + + return true; // Continue searching +} + + // Search in an index tree or subtree for all data retangles that overlap the argument rectangle. diff --git a/opennurbs_rtree.h b/opennurbs_rtree.h index b5b612fb..d6568700 100644 --- a/opennurbs_rtree.h +++ b/opennurbs_rtree.h @@ -139,6 +139,12 @@ struct ON_RTreeNode ON_RTreeBranch m_branch[ON_RTree_MAX_NODE_COUNT]; }; +// Passes data about the polyline being intersected +struct ON_RTreePolylineContext +{ + unsigned int m_polyline_pointindex; +}; + struct ON_RTreeSearchResult { int m_capacity; // m_id[] array capacity (search terminates when m_count == m_capacity) @@ -396,7 +402,8 @@ public: True if successful. */ bool CreateMeshFaceTree( const class ON_Mesh* mesh ); - + + /* Description: Insert an element into the RTree. @@ -507,11 +514,30 @@ public: void* a_context ) const; - bool Search( - ON_RTreeBBox* a_rect, + bool Search( + const ON_Line* a_line, bool ON_CALLBACK_CDECL resultCallback(void* a_context, ON__INT_PTR a_id), void* a_context - ) const; + ) const; + + bool Search( + const ON_Line* a_line, + bool infinite, + bool ON_CALLBACK_CDECL resultCallback(void* a_context, ON__INT_PTR a_id), + void* a_context + ) const; + + bool Search( + const ON_Polyline* polyline, + bool ON_CALLBACK_CDECL resultCallback(void* a_context, ON__INT_PTR a_id), + ON_RTreePolylineContext* a_context + ) const; + + bool Search( + ON_RTreeBBox* a_rect, + bool ON_CALLBACK_CDECL a_resultCallback(void* a_context, ON__INT_PTR a_id), + void* a_context + ) const; /* Description: diff --git a/opennurbs_sha1.cpp b/opennurbs_sha1.cpp index 886f1a92..c7a2d2fb 100644 --- a/opennurbs_sha1.cpp +++ b/opennurbs_sha1.cpp @@ -68,6 +68,70 @@ const ON_wString ON_SHA1_Hash::ToStringEx(bool bUpperCaseHexadecimalDigits) cons return ON_wString(L"ZeroSHA1"); return ToString(bUpperCaseHexadecimalDigits); } + +const ON_SHA1_Hash ON_SHA1_Hash::FromString( + const ON_wString string_to_parse, + bool bParseLeasingSpaces, + bool bParseInteriorSpace, + bool bParseInteriorHyphen, + ON_SHA1_Hash failure_return_value +) +{ + const wchar_t* s = static_cast(string_to_parse); + if (nullptr == s) + return failure_return_value; + + unsigned digit_count = 0; + int digits[40] = {}; + const wchar_t* skipped = nullptr; + const int len = string_to_parse.Length(); + int sdex = 0; + for (/* empty init */; sdex < len && digit_count < 40; ++sdex) + { + const int c = (int)s[sdex]; + if (c >= '0' && c <= '9') + { + digits[digit_count++] = (c - '0'); + } + else if (c >= 'A' && c <= 'F') + { + digits[digit_count++] = (c - 'A') + 10; + } + else if (c >= 'a' && c <= 'f') + { + digits[digit_count++] = (c - 'a') + 10; + } + else if ((int)(ON_wString::Space) == c) + { + if (bParseLeasingSpaces && 0 == digit_count) + continue; // skip leading white space + + if (bParseInteriorSpace && digit_count > 0 && skipped != s - 1) + skipped = s; + else + break; + } + else if ((int)(ON_wString::HyphenMinus) == c) + { + if (bParseInteriorHyphen && digit_count > 0 && skipped != s - 1) + skipped = s; + else + break; + } + else + break; + } + + if (40 != digit_count || sdex > len || (sdex < len && true == ON_wString::IsHexDigit(s[sdex]))) + return failure_return_value; + + ON_SHA1_Hash h; + int i = 0; + for(int j = 0; j < 20; ++j, ++i, ++i) + h.m_digest[j] = (ON__UINT8)(16 * digits[i] + digits[i + 1]); + return h; +} + bool ON_SHA1_Hash::Read( class ON_BinaryArchive& archive @@ -130,11 +194,21 @@ bool ON_SHA1_Hash::IsEmptyContentHash() const return 0 == ON_SHA1_Hash::Compare(*this, ON_SHA1_Hash::EmptyContentHash); } -bool ON_SHA1_Hash::IsZeroDigentOrEmptyContentHash() const +bool ON_SHA1_Hash::IsZeroDigestOrEmptyContentHash() const { return IsZeroDigest() || IsEmptyContentHash(); } +bool ON_SHA1_Hash::IsSet() const +{ + return IsZeroDigestOrEmptyContentHash() ? false : true; +} + +// OBSOLETE - spelling error in name Digent instead of Digest +bool ON_SHA1_Hash::IsZeroDigentOrEmptyContentHash() const +{ + return IsZeroDigestOrEmptyContentHash(); +} int ON_SHA1_Hash::Compare( diff --git a/opennurbs_sha1.h b/opennurbs_sha1.h index ac999156..e178cb63 100644 --- a/opennurbs_sha1.h +++ b/opennurbs_sha1.h @@ -207,6 +207,38 @@ public: bool bUpperCaseHexadecimalDigits ) const; + /* + Description: + Parse a string of 40 hexadecimal digits to create a SHA-1 hash. + Parameters: + string_to_parse - [in] + bParseLeadinglSpaces - [in] + If true, leading space characters are parsed. + Otherwise leading space characters cause parsing to fail. + bParseInteriorSpace - [in] + If true, interior space characters are parsed. + Otherwise interior space characters cause parsing to fail. + bParseInteriorHyphen - [in] + If true, interior hyphen characters are parsed. + Otherwise interior hyphen characters cause parsing to fail. + bIgnoreInternalSpaces - [in] + If true, isolated hyphens are ingored until 40 hex digits are read. + bIgnoreInternalHyphens - [in] + If true, leading spaces and isolated interior spacess are ingored until 40 hex digits are read. + failure_return_value - [in] + Value to return if string_to_parse cannot be parsed as 40 hex digits. + Returns: + If parsing is successful, the value of the SHA-1 hash is returned. + Otherwise failure_return_value is returned. + */ + static const ON_SHA1_Hash FromString( + const ON_wString string_to_parse, + bool bParseLeasingSpaces, + bool bParseInteriorSpace, + bool bParseInteriorHyphen, + ON_SHA1_Hash failure_return_value + ); + bool Read( class ON_BinaryArchive& archive ); @@ -231,8 +263,21 @@ public: */ bool IsEmptyContentHash() const; + bool IsZeroDigestOrEmptyContentHash() const; + + ON_DEPRECATED_MSG("Use IsZeroDigestOrEmptyContentHash() instead. (Spelling error in this one's name.") bool IsZeroDigentOrEmptyContentHash() const; + /* + Returns: + True if this hash is not equal to ON_SHA1_Hash::EmptyContentHash or ON_SHA1_Hash::ZeroDigest. + Remarks: + ON_SHA1_Hash::EmptyContentHash is the SHA1 of hasing zero bytes and has a non zero digest. + ON_SHA1_Hash::ZeroDigest is 20 bytes of zeros. Opennurbs uses ON_SHA1_Hash::ZeroDigest to + indicate a SHA1 has is not initialized. + */ + bool IsSet() const; + ON__UINT8 m_digest[20]; }; diff --git a/opennurbs_statics.cpp b/opennurbs_statics.cpp index 2b3e790d..b80801ec 100644 --- a/opennurbs_statics.cpp +++ b/opennurbs_statics.cpp @@ -1,4 +1,5 @@ #include "opennurbs.h" +#include "opennurbs.h" #include "opennurbs_testclass.h" #include "opennurbs_subd_data.h" @@ -514,63 +515,18 @@ static struct ON_UnicodeErrorParameters ON_Internal_UnicodeErrorParameters_Defau const struct ON_UnicodeErrorParameters ON_UnicodeErrorParameters::MaskErrors = ON_Internal_UnicodeErrorParameters_Default(0xFFFFFFFF); const struct ON_UnicodeErrorParameters ON_UnicodeErrorParameters::FailOnErrors = ON_Internal_UnicodeErrorParameters_Default(0); -// ON_wString is UTF-8 encoded -const char ON_String::Backspace = (char)ON_UnicodeCodePoint::ON_Backspace; -const char ON_String::Tab = (char)ON_UnicodeCodePoint::ON_Tab; -const char ON_String::LineFeed = (char)ON_UnicodeCodePoint::ON_LineFeed; -const char ON_String::VerticalTab = (char)ON_UnicodeCodePoint::ON_VerticalTab; -const char ON_String::FormFeed = (char)ON_UnicodeCodePoint::ON_FormFeed; -const char ON_String::CarriageReturn = (char)ON_UnicodeCodePoint::ON_CarriageReturn; -const char ON_String::Escape = (char)ON_UnicodeCodePoint::ON_Escape; -const char ON_String::Space = (char)ON_UnicodeCodePoint::ON_Space; -const char ON_String::HyphenMinus = (char)ON_UnicodeCodePoint::ON_HyphenMinus; -const char ON_String::Slash = (char)ON_UnicodeCodePoint::ON_Slash; -const char ON_String::Backslash = (char)ON_UnicodeCodePoint::ON_Backslash; -const char ON_String::Underscore = (char)ON_UnicodeCodePoint::ON_Underscore; -const char ON_String::Pipe = (char)ON_UnicodeCodePoint::ON_Pipe; -const char ON_String::Tilde = (char)ON_UnicodeCodePoint::ON_Tilde; - -// ON_wString is UTF-16 encoded when sizeof(wchar_t) = 2 -// ON_wString is UTF-32 encoded when sizeof(wchar_t) = 4 -// Never cast these values as "char" -// The UTF-8 representation of any Unicode code point with value > 127 -// requires multiple bytes. -const wchar_t ON_wString::Backspace = (wchar_t)ON_UnicodeCodePoint::ON_Backspace; -const wchar_t ON_wString::Tab = (wchar_t)ON_UnicodeCodePoint::ON_Tab; -const wchar_t ON_wString::LineFeed = (wchar_t)ON_UnicodeCodePoint::ON_LineFeed; -const wchar_t ON_wString::VerticalTab = (wchar_t)ON_UnicodeCodePoint::ON_VerticalTab; -const wchar_t ON_wString::FormFeed = (wchar_t)ON_UnicodeCodePoint::ON_FormFeed; -const wchar_t ON_wString::CarriageReturn = (wchar_t)ON_UnicodeCodePoint::ON_CarriageReturn; -const wchar_t ON_wString::Escape = (wchar_t)ON_UnicodeCodePoint::ON_Escape; -const wchar_t ON_wString::Space = (wchar_t)ON_UnicodeCodePoint::ON_Space; -const wchar_t ON_wString::HyphenMinus = (wchar_t)ON_UnicodeCodePoint::ON_HyphenMinus; -const wchar_t ON_wString::Slash = (wchar_t)ON_UnicodeCodePoint::ON_Slash; -const wchar_t ON_wString::Backslash = (wchar_t)ON_UnicodeCodePoint::ON_Backslash; -const wchar_t ON_wString::Underscore = (char)ON_UnicodeCodePoint::ON_Underscore; -const wchar_t ON_wString::Pipe = (wchar_t)ON_UnicodeCodePoint::ON_Pipe; -const wchar_t ON_wString::Tilde = (wchar_t)ON_UnicodeCodePoint::ON_Tilde; -const wchar_t ON_wString::DecimalAsPeriod = (wchar_t)ON_UnicodeCodePoint::ON_Period; -const wchar_t ON_wString::DecimalAsComma = (wchar_t)ON_UnicodeCodePoint::ON_Comma; - -#if defined(ON_SIZEOF_WCHAR_T) && ON_SIZEOF_WCHAR_T >= 2 -// ON_wString is UTF-16 encoded when sizeof(wchar_t) = 2 -// ON_wString is UTF-32 encoded when sizeof(wchar_t) = 4 -const wchar_t ON_wString::DegreeSymbol = (wchar_t)ON_UnicodeCodePoint::ON_DegreeSymbol; -const wchar_t ON_wString::RadiusSymbol = (wchar_t)ON_UnicodeCodePoint::ON_RadiusSymbol; -const wchar_t ON_wString::DiameterSymbol = (wchar_t)ON_UnicodeCodePoint::ON_DiameterSymbol; -const wchar_t ON_wString::PlusMinusSymbol = (wchar_t)ON_UnicodeCodePoint::ON_PlusMinusSymbol; -const wchar_t ON_wString::RecyclingSymbol = (wchar_t)ON_UnicodeCodePoint::ON_RecyclingSymbol; -const wchar_t ON_wString::ReplacementCharacter = (wchar_t)ON_UnicodeCodePoint::ON_ReplacementCharacter; -const wchar_t ON_wString::NextLine = (wchar_t)ON_UnicodeCodePoint::ON_NextLine; -const wchar_t ON_wString::LineSeparator = (wchar_t)ON_UnicodeCodePoint::ON_LineSeparator; -const wchar_t ON_wString::ParagraphSeparator = (wchar_t)ON_UnicodeCodePoint::ON_ParagraphSeparator; -const wchar_t ON_wString::NoBreakSpace = (wchar_t)ON_UnicodeCodePoint::ON_NoBreakSpace; -const wchar_t ON_wString::NarrowNoBreakSpace = (wchar_t)ON_UnicodeCodePoint::ON_NarrowNoBreakSpace; -const wchar_t ON_wString::ZeroWidthSpace = (wchar_t)ON_UnicodeCodePoint::ON_ZeroWidthSpace; -#endif - const ON_String ON_String::EmptyString; +static const ON_String ON_Internal_ByteOrderMark() +{ + // UTF-8 encoded byte order mark. + const unsigned char bom[3] = {0xEFU,0xBBU,0xBFU}; + return ON_String((const char*)bom); +} +const ON_String ON_String::ByteOrderMark(ON_Internal_ByteOrderMark()); + const ON_wString ON_wString::EmptyString; +const ON_wString ON_wString::ByteOrderMark((wchar_t)ON_UnicodeCodePoint::ON_ByteOrderMark); + const ON_NameHash ON_NameHash::UnsetNameHash ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_NameHash); const ON_NameHash ON_NameHash::EmptyNameHash = ON_NameHash::CreateIdAndEmptyName(ON_nil_uuid); const ON_wString ON_ModelComponent::ReferencePrefixDelimiter(L" : "); @@ -690,7 +646,10 @@ 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); -const double ON_Symmetry::ZeroTolerance = 1.0e-8; +// Do not increase this tolerance to fix a specific bug. +// This tolerance is used after input has been cleaned up +// to detect flaws. +const double ON_Symmetry::ZeroTolerance = 1.0e-8; const ON_Symmetry ON_Symmetry::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Symmetry); @@ -902,6 +861,10 @@ const ON_Line ON_Line::NanLine(ON_3dPoint::NanPoint, ON_3dPoint::NanPoint); const ON_PlaneEquation ON_PlaneEquation::UnsetPlaneEquation(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); const ON_PlaneEquation ON_PlaneEquation::ZeroPlaneEquation(0.0, 0.0, 0.0, 0.0); const ON_PlaneEquation ON_PlaneEquation::NanPlaneEquation(ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN); +const ON_PlaneEquation ON_PlaneEquation::WorldXY(0.0, 0.0, 1.0, 0.0); +const ON_PlaneEquation ON_PlaneEquation::WorldYZ(1.0, 0.0, 0.0, 0.0); +const ON_PlaneEquation ON_PlaneEquation::WorldZX(0.0, 1.0, 0.0, 0.0); + const ON_Plane ON_Plane::World_xy(ON_3dPoint::Origin, ON_3dVector::XAxis, ON_3dVector::YAxis); const ON_Plane ON_Plane::World_yz(ON_3dPoint::Origin, ON_3dVector::YAxis, ON_3dVector::ZAxis); @@ -1413,7 +1376,7 @@ ON_Internal_FontGlyphPool ON_Internal_FontGlyphPool::theGlyphItemPool; // ON_ManagedFonts::List needs to be allocated before ON_Font::Default // This list of installed fonts is used to initialize ON_Font::Default. -ON_ManagedFonts ON_ManagedFonts::List ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ManagedFonts); +ON_ManagedFonts ON_ManagedFonts::List((ON__UINT_PTR)0); // The ON_PTR_SEMAPHORE1 parameter to the // ON_Font::ON_Font(const ON_Font&) copy ctor triggers special @@ -2345,7 +2308,7 @@ static ON_DimStyle DimStyleFeetEngrave() ON_DimStyle dimstyle; DimStyleInit(L"Feet Engrave", -10, id, dimstyle); DimStyleFeetEngraveInit(dimstyle); - const ON_Font* font = ON_Font::InstalledFontFromRichTextProperties(L"SLF-RHN Architect", false, false); + const ON_Font* font = ON_Font::DefaultEngravingFont(); if (nullptr != font) dimstyle.SetFont(*font); Internal_SystemDimStyleFinalize(dimstyle); @@ -2360,7 +2323,7 @@ static ON_DimStyle DimStyleMillimeterEngrave() ON_DimStyle dimstyle; DimStyleInit(L"Millimeter Engrave", -11, id, dimstyle); DimStyleMillimeterEngraveInit(dimstyle); - const ON_Font* font = ON_Font::InstalledFontFromRichTextProperties(L"SLF-RHN Architect", false, false); + const ON_Font* font = ON_Font::DefaultEngravingFont(); if (nullptr != font) dimstyle.SetFont(*font); Internal_SystemDimStyleFinalize(dimstyle); @@ -2375,7 +2338,7 @@ static ON_DimStyle DimStyleModelUnitsEngrave() ON_DimStyle dimstyle; DimStyleInit(L"Model Units Engrave", -12, id, dimstyle); DimStyleModelUnitsEngraveInit(dimstyle); - const ON_Font* font = ON_Font::InstalledFontFromRichTextProperties(L"SLF-RHN Architect", false, false); + const ON_Font* font = ON_Font::DefaultEngravingFont(); if (nullptr != font) dimstyle.SetFont(*font); Internal_SystemDimStyleFinalize(dimstyle); @@ -2695,6 +2658,13 @@ const ON_SubDFacePtr ON_SubDFacePtr::Null = { 0 }; const ON_SubDComponentPtr ON_SubDComponentPtr::Null = { 0 }; const ON_SubDComponentPtrPair ON_SubDComponentPtrPair::Null = ON_SubDComponentPtrPair::Create(ON_SubDComponentPtr::Null,ON_SubDComponentPtr::Null); const ON_SubDComponentList ON_SubDComponentList::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDComponentList); +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubD_ComponentIdTypeAndTag); + +const ON_SubDComponentId ON_SubDComponentIdUnset(ON_SubDComponentPtr::Type::Unset, 0U); + +// Passes() returns true for every non nullptr component. +const ON_SubDComponentTest ON_SubDComponentTest::AllPass((ON__UINT_PTR)1); +const ON_SubDComponentTest ON_SubDComponentTest::AllFail((ON__UINT_PTR)0); const ON_SubDEdgeChain ON_SubDEdgeChain::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDEdgeChain); diff --git a/opennurbs_string.cpp b/opennurbs_string.cpp index a214582b..7d771da8 100644 --- a/opennurbs_string.cpp +++ b/opennurbs_string.cpp @@ -853,6 +853,15 @@ const char* ON_String::Array() const return ( Header()->string_capacity > 0 ) ? m_s : 0; } +const ON_String ON_String::Duplicate() const +{ + if (Length() <= 0) + return ON_String::EmptyString; + ON_String s = *this; + s.CopyArray(); + return s; +} + /* Returns: Total number of bytes of memory used by this class. diff --git a/opennurbs_string.h b/opennurbs_string.h index bbad8d9d..3a83b09e 100644 --- a/opennurbs_string.h +++ b/opennurbs_string.h @@ -17,7 +17,6 @@ #if !defined(ON_STRING_INC_) #define ON_STRING_INC_ - /* Description: Sort an index array. @@ -67,7 +66,7 @@ void ON_Sort( Description: Sort an index array using a compare function that takes an additional pointer that can be used to - pass extra informtation. + pass extra information. Parameters method - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort. @@ -875,6 +874,10 @@ const ON_SHA1_Hash ON_StringContentHash( ON_StringMapOrdinalType mapping ); +/// +/// A char string. +/// Any multibyte encoding can be used. If the encoding is unknown, assume it is UTF-8. +/// class ON_CLASS ON_String { public: @@ -885,36 +888,134 @@ public: enum : int { - // This ON_String::MaximumStringLength value of 100,000,000 - // is used for string length sanity checking in both ON_String and - // ON_wString. - // The design of the ON_String and ON_wString classes could support - // string lengths up to 0xFFFFFFFEU = 4,294,967,294 but - // a cap of 100 million seems generous enough for current usage - // and is small enough to detect many corrupt memory - // situations. - // This value is used for both ON_String and ON_wString. + /// + /// This ON_String::MaximumStringLength value of 100,000,000 + /// is used for string length sanity checking in both ON_String and + /// ON_wString. + /// The design of the ON_String and ON_wString classes could support + /// string lengths up to 0xFFFFFFFEU = 4,294,967,294 but + /// a cap of 100 million seems generous enough for current usage + /// and is small enough to detect many corrupt memory + /// situations. + /// This value is used for both ON_String and ON_wString. + /// MaximumStringLength = 100000000 }; - // ON_String::EmptyString has length 0. - // const char* s = ON_String::EmptyString sets s to "". + /// + /// ON_String::EmptyString has length 0. + /// const char* s = ON_String::EmptyString sets s = ""; + /// static const ON_String EmptyString; - static const char Backspace; // Unicode BACKSPACE control U+0008 - static const char Tab; // Unicode CHARACTER TABULATION control U+0009 - static const char LineFeed; // Unicode LINE FEED control U+000A - static const char VerticalTab; // Unicode LINE TABULATION control U+000B - static const char FormFeed; // Unicode FORM FEED control U+000C - static const char CarriageReturn; // Unicode CHARACTER TABULATION control U+000D - static const char Escape; // Unicode CARRIAGE RETURN control U+001B - static const char Space; // Unicode SPACE U+0020 - static const char HyphenMinus; // Unicode SPACE U+002D - static const char Slash; // Unicode SOLIDUS U+002F - static const char Backslash; // Unicode REVERSE SOLIDUS U+005C - static const char Underscore; // Unicode LOW LINE U+005F - static const char Pipe; // Unicode VERTICAL LINE U+007C - static const char Tilde; // Unicode TILDE U+007E + /// + /// Even though a char string has endian independent byte order, + /// it is valid for UTF-8 encoded text to begin with the UTF-8 encoding of U+FEFF. + /// A UTF-8 BOM is sometimes used to mark a char string as UTF-8 encoded. + /// A UTF-8 BOM can occur when UTF-16 and UTF-32 encoded text with a byte + /// order mark is converted to UTF-8 encoded text. Conversely a UTF-8 BOM + /// is sometimes used when UTF-8 encode text will be converted to UTF-16/UTF-32 + /// encoded text and a BOM is desired in the result. + /// + static const ON_String ByteOrderMark; + + /// BACKSPACE control U+0008 + static const char Backspace = (char)ON_UnicodeCodePoint::ON_Backspace; + + /// CHARACTER TABULATION control U+0009 + static const char Tab = (char)ON_UnicodeCodePoint::ON_Tab; + + /// LINE FEED control U+000A + static const char LineFeed = (char)ON_UnicodeCodePoint::ON_LineFeed; + + /// LINE TABULATION control U+000B + static const char VerticalTab = (char)ON_UnicodeCodePoint::ON_VerticalTab; + + /// FORM FEED control U+000C + static const char FormFeed = (char)ON_UnicodeCodePoint::ON_FormFeed; + + /// CARRIAGE RETURN control U+000D + static const char CarriageReturn = (char)ON_UnicodeCodePoint::ON_CarriageReturn; + + /// ESCAPE control U+001B + static const char Escape = (char)ON_UnicodeCodePoint::ON_Escape; + + /// SPACE U+0020 + static const char Space = (char)ON_UnicodeCodePoint::ON_Space; + + /// QUOTATION MARK U+0022 (") + static const char QuotationMark = (char)ON_UnicodeCodePoint::ON_QuotationMark; + + /// NUMBER SIGN U+0023 (#) + static const char NumberSign = (char)ON_UnicodeCodePoint::ON_NumberSign; + + /// PERCENT SIGN U+0025 (%) + static const char PercentSign = (char)ON_UnicodeCodePoint::ON_PercentSign; + + /// AMPERSAND U+0026 (&) + static const char Ampersand = (char)ON_UnicodeCodePoint::ON_Ampersand; + + /// APOSTROPHE U+0027 (') + static const char Apostrophe = (char)ON_UnicodeCodePoint::ON_Apostrophe; + + /// COMMA U+002C (,) + static const char Comma = (char)ON_UnicodeCodePoint::ON_Comma; + + /// HYPHEN-MINUS U+002D (-) + static const char HyphenMinus = (char)ON_UnicodeCodePoint::ON_HyphenMinus; + + /// PERIOD U+002E (decimal 46) (.) + static const char Period = (char)ON_UnicodeCodePoint::ON_Period; + + /// SOLIDUS U+002F (/) + static const char Slash = (char)ON_UnicodeCodePoint::ON_Slash; + + /// COLON U+003A (:) + static const char Colon = (char)ON_UnicodeCodePoint::ON_Colon; + + /// SEMICOLON U+003B (;) + static const char Semicolon = (char)ON_UnicodeCodePoint::ON_Semicolon; + + /// LESS-THAN SIGN U+003C (<) + static const char LessThanSign = (char)ON_UnicodeCodePoint::ON_LessThanSign; + + /// GREATER-THAN SIGN U+003E (>) + static const char GreaterThanSign = (char)ON_UnicodeCodePoint::ON_GreaterThanSign; + + /// REVERSE SOLIDUS U+005C (\) + static const char Backslash = (char)ON_UnicodeCodePoint::ON_Backslash; + + /// // Unicode LOW LINE U+005F (_) + static const char Underscore = (char)ON_UnicodeCodePoint::ON_Underscore; + + /// VERTICAL LINE U+007C (|) + static const char Pipe = (char)ON_UnicodeCodePoint::ON_Pipe; + + /// TILDE U+007E (~) + static const char Tilde = (char)ON_UnicodeCodePoint::ON_Tilde; + + /// Period decimal point (.) + static const char DecimalAsPeriod = (char)ON_UnicodeCodePoint::ON_Period; + + /// Comma decimal point (,) + static const char DecimalAsComma = (char)ON_UnicodeCodePoint::ON_Comma; + + + /* + Parameters: + c - [in] + Returns: + True if c is '0', '1', ..., '9', 'A', 'B', ..., 'F', 'a', 'b', ..., of 'f'. + */ + static bool IsHexDigit(char c); + + /* + Parameters: + c - [in] + Returns: + True if c is '0', '1', ..., or '9'. + */ + static bool IsDecimalDigit(char c); private: // Use IsEmpty() or IsNotEmpty() when you want a bool @@ -1954,6 +2055,13 @@ public: char* Array(); const char* Array() const; + /* + Returns: + A duplicate of this that does not share memory with any other string. + (A new array is allocated for the returned string.) + */ + const ON_String Duplicate() const; + /* Returns: Total number of bytes of memory used by this class. @@ -2122,57 +2230,476 @@ Returns: ON_DECL bool operator>=(const char* lhs, const ON_String& rhs); -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -// -// ON_wString -// - +/// +/// A wide character string. +/// The default encoding is the encoding the compiler uses for wchar_t* s = L"..."; strings. +/// This is typically 2 byte wchar_t UTF-16 on Windows and 4 byte wchar_t UTF-32 on MacOS. +/// However, some MacOS SDK functions return 4 byte wchar_t UTF-16 strings. +/// class ON_CLASS ON_wString { public: - - // ON_String::EmptyString has length 0. - // const char* s = ON_String::EmptyString sets s to L"". + /// + /// ON_String::EmptyString has length 0. + /// const char* s = ON_String::EmptyString sets s to L"". + /// static const ON_wString EmptyString; - static const wchar_t Backspace; // Unicode BACKSPACE control U+0008 - static const wchar_t Tab; // Unicode CHARACTER TABULATION control U+0009 - static const wchar_t LineFeed; // Unicode LINE FEED control U+000A - static const wchar_t VerticalTab; // Unicode LINE TABULATION control U+000B - static const wchar_t FormFeed; // Unicode FORM FEED control U+000C - static const wchar_t CarriageReturn; // Unicode CARRIAGE RETURN control U+000D - static const wchar_t Escape; // Unicode CARRIAGE RETURN control U+001B - static const wchar_t Space; // Unicode SPACE U+0020 - static const wchar_t HyphenMinus; // Unicode SPACE U+002D - static const wchar_t Slash; // Unicode SOLIDUS U+002F - static const wchar_t Backslash; // Unicode REVERSE SOLIDUS U+005C - static const wchar_t Underscore; // Unicode LOW LINE U+005F - static const wchar_t Pipe; // Unicode VERTICAL LINE U+007C - static const wchar_t Tilde; // Unicode TILDE U+007E - static const wchar_t DecimalAsPeriod; // Unicode PERIOD U+002E - static const wchar_t DecimalAsComma; // Unicode COMMA U+002C + /// + /// UTF-16/UTF-32 encoding of the Unicode byte order mark (BOM) U+FEFF. + /// + static const ON_wString ByteOrderMark; + + /// + /// Identifies a built in string that can be used for testing. + /// + enum class ExampleType : unsigned int + { + /// + /// ON_wString::EmptyString + /// + Empty = 0, + + /// + /// A wchar_t string that contains code points with 2, 3, 4, and 5 hex digit code points. + /// Useful for testing what a compiler does with string that have UTF-8 multiple byte encodings and UTF-16 surrogate pair encodings. + /// "The math teacher said, \"It isn't true that Σ > 3¢ & Σ < 2 ₽ & Σ > €99.\" 🗑!" + /// + WideChar = 1, + + /// + /// The WideChar string UTF-16 encoded. + /// + UTF16 = 51, + + /// + /// The rich text string from ON_wString::RichTextExample(&ON_FOnt::Default). + /// + RichText = 90, + + /// + /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin UTF-16 encoded. + /// + XML = 101, + + /// + /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin encoded in the &#hhhh; format + /// using lower case hex digits (0123456789abcdef). + /// + XMLalternate1 = 102, + + /// + /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin encoded in the hexadecimal &#xhhhh; format + /// with upper case hex digits (0123456789ABCDEF). + /// + XMLalternate2 = 103, + + /// + /// The WideChar string as an XML value with special characters and code points above + /// basic latin encoded in the decimal code point &#nnnn; format. + /// + XMLalternate3 = 104, + }; + + + /// + /// A selection of strings that can be used for testing. + /// + static const ON_wString Example(ON_wString::ExampleType t); + + /// + /// Get a rich text example. + /// + /// + /// The rich text font name. + /// This name is not well defined and the best choice can be platform specific. + /// For Windows use the LOGFONT always works. + /// For Mac OS the font family name generally works. + /// If you have an ON_Font, then ON_Font.RichTextFontName() or ON_Font.FontQuartet().QuartetName() are good choices. + /// + /// Pass true to include a rich text bold face line. + /// Pass true to include a rich text italic face line. + /// Pass true to include a rich text bold-italic face line. + /// Pass true to include both plain and underline in the sample. + /// + /// A rich text example using the specified face and the specified + /// + static const ON_wString RichTextExample( + ON_wString rich_text_font_name, + bool bBold, + bool bItalic, + bool bBoldItalic, + bool bUnderline + ); + + /// + /// Get a rich text example. + /// + /// + /// Every rich text face supported by font will be in the sample. + /// + /// + /// A rich text example using the specified font in all supported rich text faces (regular/bold/italic/bold-italic) in both plain and underline. + /// + static const ON_wString RichTextExample( + const class ON_Font* font + ); + + /// + /// Get a rich text example. + /// + /// + /// Every rich text face supported by the font quartet will be in the sample. + /// + /// + /// A rich text example using the specified font in all supported rich text faces (regular/bold/italic/bold-italic) in both plain and underline. + /// + static const ON_wString RichTextExample( + const class ON_FontFaceQuartet* quartet + ); + +public: + /// BACKSPACE control U+0008 + static const wchar_t Backspace = (wchar_t)ON_UnicodeCodePoint::ON_Backspace; + + /// CHARACTER TABULATION control U+0009 + static const wchar_t Tab = (wchar_t)ON_UnicodeCodePoint::ON_Tab; + + /// LINE FEED control U+000A + static const wchar_t LineFeed = (wchar_t)ON_UnicodeCodePoint::ON_LineFeed; + + /// LINE TABULATION control U+000B + static const wchar_t VerticalTab = (wchar_t)ON_UnicodeCodePoint::ON_VerticalTab; + + /// FORM FEED control U+000C + static const wchar_t FormFeed = (wchar_t)ON_UnicodeCodePoint::ON_FormFeed; + + /// CARRIAGE RETURN control U+000D + static const wchar_t CarriageReturn = (wchar_t)ON_UnicodeCodePoint::ON_CarriageReturn; + + /// ESCAPE control U+001B + static const wchar_t Escape = (wchar_t)ON_UnicodeCodePoint::ON_Escape; + + /// SPACE U+0020 + static const wchar_t Space = (wchar_t)ON_UnicodeCodePoint::ON_Space; + + /// QUOTATION MARK U+0022 (") + static const wchar_t QuotationMark = (wchar_t)ON_UnicodeCodePoint::ON_QuotationMark; + + /// NUMBER SIGN U+0023 (#) + static const wchar_t NumberSign = (wchar_t)ON_UnicodeCodePoint::ON_NumberSign; + + /// PERCENT SIGN U+0025 (%) + static const wchar_t PercentSign = (wchar_t)ON_UnicodeCodePoint::ON_PercentSign; + + /// AMPERSAND U+0026 (&) + static const wchar_t Ampersand = (wchar_t)ON_UnicodeCodePoint::ON_Ampersand; + + /// APOSTROPHE U+0027 (') + static const wchar_t Apostrophe = (wchar_t)ON_UnicodeCodePoint::ON_Apostrophe; + + /// COMMA U+002C (,) + static const wchar_t Comma = (wchar_t)ON_UnicodeCodePoint::ON_Comma; + + /// HYPHEN-MINUS U+002D (-) + static const wchar_t HyphenMinus = (wchar_t)ON_UnicodeCodePoint::ON_HyphenMinus; + + /// PERIOD U+002E (decimal 46) (.) + static const wchar_t Period = (wchar_t)ON_UnicodeCodePoint::ON_Period; + + /// SOLIDUS U+002F (/) + static const wchar_t Slash = (wchar_t)ON_UnicodeCodePoint::ON_Slash; + + /// COLON U+003A (:) + static const wchar_t Colon = (wchar_t)ON_UnicodeCodePoint::ON_Colon; + + /// SEMICOLON U+003B (;) + static const wchar_t Semicolon = (wchar_t)ON_UnicodeCodePoint::ON_Semicolon; + + /// LESS-THAN SIGN U+003C (<) + static const wchar_t LessThanSign = (wchar_t)ON_UnicodeCodePoint::ON_LessThanSign; + + /// GREATER-THAN SIGN U+003E (>) + static const wchar_t GreaterThanSign = (wchar_t)ON_UnicodeCodePoint::ON_GreaterThanSign; + + /// REVERSE SOLIDUS U+005C (\) + static const wchar_t Backslash = (wchar_t)ON_UnicodeCodePoint::ON_Backslash; + + /// // Unicode LOW LINE U+005F (_) + static const wchar_t Underscore = (wchar_t)ON_UnicodeCodePoint::ON_Underscore; + + /// VERTICAL LINE U+007C (|) + static const wchar_t Pipe = (wchar_t)ON_UnicodeCodePoint::ON_Pipe; + + /// TILDE U+007E (~) + static const wchar_t Tilde = (wchar_t)ON_UnicodeCodePoint::ON_Tilde; + + /// Period decimal point (.) + static const wchar_t DecimalAsPeriod = (wchar_t)ON_UnicodeCodePoint::ON_Period; + + /// Comma decimal point (,) + static const wchar_t DecimalAsComma = (wchar_t)ON_UnicodeCodePoint::ON_Comma; #if defined(ON_SIZEOF_WCHAR_T) && ON_SIZEOF_WCHAR_T >= 2 - // Never cast these values as "char" - // The UTF-8 representation of any Unicode code point with value > 127 - // requires multiple bytes. - static const wchar_t RadiusSymbol; // Unicode LATIN CAPITAL LETTER R U+0052 - static const wchar_t DegreeSymbol; // Unicode DEGREE SIGN U+00B0 - static const wchar_t PlusMinusSymbol; // Unicode PLUS-MINUS SIGN U+00B1 - static const wchar_t DiameterSymbol; // Unicode LATIN CAPITAL LETTER O WITH STROKE U+00D8 - static const wchar_t RecyclingSymbol; // Unicode UNIVERSAL RECYCLING SYMBOL U+2672 (decimal 9842) - static const wchar_t ReplacementCharacter; // Unicode REPLACEMENT CHARACTER U+FFFD - static const wchar_t NextLine; // Unicode NEXT LINE (NEL) U+0085 - static const wchar_t LineSeparator; // LINE SEPARATOR U+2028 unambiguous line separator - static const wchar_t ParagraphSeparator; // PARAGRAPH SEPARATOR U+2028 unambiguous paragraph separator - static const wchar_t NoBreakSpace; // NO-BREAK SPACE (NBSP) - static const wchar_t NarrowNoBreakSpace; // NARROW NO-BREAK SPACE (NNBSP) - static const wchar_t ZeroWidthSpace; // ZERO WIDTH SPACE (ZWSP) +public: + /// NEXT LINE (NEL) control U+0085 + static const wchar_t NextLine = (wchar_t)ON_UnicodeCodePoint::ON_NextLine; + + /// NO-BREAK SPACE (NBSP) U+00A0 + static const wchar_t NoBreakSpace = (wchar_t)ON_UnicodeCodePoint::ON_NoBreakSpace; + + /// NON-BREAKING HYPHEN U+2011 + static const wchar_t NoBreakHyphen = (wchar_t)ON_UnicodeCodePoint::ON_NoBreakHyphen; + + /// ZERO WIDTH SPACE (ZWSP) U+200B + static const wchar_t ZeroWidthSpace = (wchar_t)ON_UnicodeCodePoint::ON_ZeroWidthSpace; + + /// zero with non-joiner (ZWNJ) U+200C + static const wchar_t ZeroWidthNonJoiner = (wchar_t)ON_UnicodeCodePoint::ON_ZeroWidthNonJoiner; + + /// zero with joiner (ZWJ) U+200D + static const wchar_t ZeroWidthJoiner = (wchar_t)ON_UnicodeCodePoint::ON_ZeroWidthJoiner; + + /// NARROW NO-BREAK SPACE (NNBSP) U+202F + static const wchar_t NarrowNoBreakSpace = (wchar_t)ON_UnicodeCodePoint::ON_NarrowNoBreakSpace; + + /// LATIN CAPITAL LETTER R U+0052 (decimal 82) (Rhino annotation radius symbol) + static const wchar_t RadiusSymbol = (wchar_t)ON_UnicodeCodePoint::ON_RadiusSymbol; + + /// DEGREE SIGN U+00B0 (X°) (Rhino annotation degree symbol) + static const wchar_t DegreeSymbol = (wchar_t)ON_UnicodeCodePoint::ON_DegreeSymbol; + + /// PLUS-MINUS SIGN U+00B1 (±) (Rhino annotation plus/minus symbol) + static const wchar_t PlusMinusSymbol = (wchar_t)ON_UnicodeCodePoint::ON_PlusMinusSymbol; + + /// SUPERSCRIPT TWO U+00B2 (X²) (Rhino annotation length squared symbol) + static const wchar_t Superscript2 = (wchar_t)ON_UnicodeCodePoint::ON_Superscript2; + + /// SUPERSCRIPT THREE U+00B3 (X³) (Rhino annotation length cubed symbol) + static const wchar_t Superscript3 = (wchar_t)ON_UnicodeCodePoint::ON_Superscript3; + + /// LATIN CAPITAL LETTER O WITH STROKE U+00D8 (Ø) (Rhino annotation diametersymbol) + static const wchar_t DiameterSymbol = (wchar_t)ON_UnicodeCodePoint::ON_DiameterSymbol; + + /// LINE SEPARATOR U+2028 unambiguous line separator + static const wchar_t LineSeparator = (wchar_t)ON_UnicodeCodePoint::ON_LineSeparator; + + /// PARAGRAPH SEPARATOR U+2028 unambiguous paragraph separator + static const wchar_t ParagraphSeparator = (wchar_t)ON_UnicodeCodePoint::ON_ParagraphSeparator; + + /// GREEK SMALL LETTER ALPHA (Α) + static const wchar_t GreekCapitalAlpha = (wchar_t)ON_UnicodeCodePoint::ON_GreekCapitalAlpha; + + /// GREEK SMALL LETTER ALPHA (α) + static const wchar_t GreekAlpha = (wchar_t)ON_UnicodeCodePoint::ON_GreekAlpha; + + /// GREEK CAPITAL LETTER SIGMA U+03A3 (Σ) + static const wchar_t GreekCapitalSigma = (wchar_t)ON_UnicodeCodePoint::ON_GreekCapitalSigma; + + /// GREEK SMALL LETTER SIGMA U+03C3 (σ) + static const wchar_t GreekSigma = (wchar_t)ON_UnicodeCodePoint::ON_GreekSigma; + + /// GREEK SMALL LETTER OMEGA U+03A9 (Ω) + static const wchar_t GreekCapitalOmega = (wchar_t)ON_UnicodeCodePoint::ON_GreekCapitalOmega; + + /// GREEK SMALL LETTER OMEGA U+03C9 (ω) + static const wchar_t GreekOmega = (wchar_t)ON_UnicodeCodePoint::ON_GreekOmega; + + /// CYRILLIC CAPITAL LETTER YU U+042E (Ю) (Used in Cyrillic code point tests) + static const wchar_t CyrillicCapitalYu = (wchar_t)ON_UnicodeCodePoint::ON_CyrillicCapitalYu; + + /// Simplified Chinese logogram for tree U+6881 (梁) (Used in CJK code point tests) + static const wchar_t SimplifiedChineseTree = (wchar_t)ON_UnicodeCodePoint::ON_SimplifiedChineseTree; + + /// Traditional Chinese logogram for tree U+6A39 (樹) (Used in CJK code point tests) + static const wchar_t TraditionalChineseTree = (wchar_t)ON_UnicodeCodePoint::ON_TraditionalChineseTree; + + /// Japanese logogram for rhinoceros U+7280 (犀) (Used in CJK code point tests) + static const wchar_t JapaneseRhinoceros = (wchar_t)ON_UnicodeCodePoint::ON_JapaneseRhinoceros; + + /// Japanese logogram for tree U+6728 (木) (Used in CJK code point tests) + static const wchar_t JapaneseTree = (wchar_t)ON_UnicodeCodePoint::ON_JapaneseTree; + + /// Korean HAN U+D55C (한) (Used in CJK code point tests) + static const wchar_t KoreanHan = (wchar_t)ON_UnicodeCodePoint::ON_KoreanHan; + + /// Korean JEONG U+C815 (정) (Used in CJK code point tests) + static const wchar_t KoreanJeong = (wchar_t)ON_UnicodeCodePoint::ON_KoreanJeong; + + /// DOLLAR SIGN U+0024 ($) + static const wchar_t DollarSign = (wchar_t)ON_UnicodeCodePoint::ON_DollarSign; + + /// CENT SIGN U+00A2 (¢) + static const wchar_t CentSign = (wchar_t)ON_UnicodeCodePoint::ON_CentSign; + + /// POUND SIGN U+00A3 (£) + static const wchar_t PoundSign = (wchar_t)ON_UnicodeCodePoint::ON_PoundSign; + + /// CURRENCY SIGN U+00A4 (¤) + static const wchar_t CurrencySign = (wchar_t)ON_UnicodeCodePoint::ON_CurrencySign; + + /// YEN SIGN U+00A5 (Chinese yuan; Japanese yen) (¥) + static const wchar_t YenSign = (wchar_t)ON_UnicodeCodePoint::ON_YenSign; + + /// EURO SIGN U+20AC (€) + static const wchar_t EuroSign = (wchar_t)ON_UnicodeCodePoint::ON_EuroSign; + + /// PESO SIGN U+20B1 (₱) + static const wchar_t PesoSign = (wchar_t)ON_UnicodeCodePoint::ON_PesoSign; + + /// RUBLE SIGN U+20BD (₽) + static const wchar_t RubleSign = (wchar_t)ON_UnicodeCodePoint::ON_RubleSign; + + /// + /// UNIVERSAL RECYCLING SYMBOL U+2672 (♲) + /// This is a good cold point for testing glyph substitution. + /// + static const wchar_t RecyclingSymbol = (wchar_t)ON_UnicodeCodePoint::ON_RecyclingSymbol; + + + /// + /// BLACK UNIVERSAL RECYCLING SYMBOL U+267B (♻) + /// This is a good cold point for testing glyph substitution. + /// + static const wchar_t BlackRecyclingSymbol = (wchar_t)ON_UnicodeCodePoint::ON_BlackRecyclingSymbol; + + /// + /// REPLACEMENT CHARACTER U+FFFD (�) + /// By convention, U+FFFD is used to mark string elements where + /// an invalid UTF code point encoding was encountered. + /// + static const wchar_t ReplacementCharacter = (wchar_t)ON_UnicodeCodePoint::ON_ReplacementCharacter; #endif + /* + Parameters: + c - [in] + Returns: + True if c is '0', '1', ..., '9', 'A', 'B', ..., 'F', 'a', 'b', ..., of 'f'. + */ + static bool IsHexDigit(wchar_t c); + + /* + Parameters: + c - [in] + Returns: + True if c is '0', '1', ..., or '9'. + */ + static bool IsDecimalDigit(wchar_t c); + + + /// + /// Determine if c is a decimal digit. + /// + /// + /// character to test + /// + /// + /// Result to return when c is an ordinary decimal digit (0123456789) + /// + /// + /// Result to return when c is a sperscript decimal digit (0123456789) + /// + /// + /// Result to return when c is a subscript decimal digit (0123456789) + /// + /// + /// True if c is a decimal digit. + /// + static bool IsDecimalDigit( + wchar_t c, + bool bOrdinaryDigitResult, + bool bSuperscriptDigitResult, + bool bSubscriptDigitResult + ); + + static unsigned DecimalDigitFromWideChar( + wchar_t c, + bool bAcceptOrdinaryDigit, + bool bAcceptSuperscriptDigit, + bool bAcceptSubscriptDigit, + unsigned invalid_c_result + ); + + /* + Parameters: + c - [in] + character to test. + bAcceptOrdinarySign - [in] + ordinary + and - signes are acceptable. + bAcceptSuperscriptSign - [in] + superscript + and - signes are acceptable. + bAcceptSubscriptSign - [in] + subscript + and - signes are acceptable. + Returns: + +1 if c is an acceptable plus sign. + -1 if c is an acceptable minus sign. + Otherwise, 0 is returned. + */ + static int PlusOrMinusSignFromWideChar( + wchar_t c, + bool bAcceptOrdinarySign, + bool bAcceptSuperscriptSign, + bool bAcceptSubscriptSign + ); + + /// + /// Determine if c is some type opf Unicode slash (solidus). + /// + /// + /// character to test + /// + /// + /// Result to return when c is an ordinary slash (solidus) U+002F (/) + /// + /// + /// Result to return when c is a fraction slash U+2044 (⁄) + /// + /// + /// Result to return when c is a division slash U+2215 (∕) + /// + /// + /// Result to return when c is a mathematical rising diagonal slash U+27CB (⟋) + /// + /// + static bool IsSlash( + wchar_t c, + bool bOrdinarySlashResult, + bool bFractionSlashResult, + bool bDivisionSlashResult, + bool bMathematicalSlashResult + ); + + /* + Parameters: + c - [in] + character to test. + bTabResult - [in] + Result to return when c is a horizontal tab. + bNoBreakSpaceResult - [in] + Result to return when c is some type of no break space. + bZeroWidthSpaceResult - [in] + Result to return when c is a zero width space code point. + Returns: + True if c is some type of horizontal space. + */ + static bool IsHorizontalSpace(wchar_t c, bool bTabResult, bool bNoBreakSpaceResult, bool bZeroWidthSpaceResult); + + /* + Parameters: + c - [in] + character to test. + Returns: + True if c is some type of horizontal space, including horizontal tab and zero width spaces. + If you need a more nuanced test, call the version of IsHorizontalSpace() that + has bool parameters. + */ + static bool IsHorizontalSpace(wchar_t c); + + private: // Use IsEmpty() or IsNotEmpty() when you want a bool // to test for the empty string. @@ -2183,6 +2710,20 @@ public: ON_wString() ON_NOEXCEPT; ON_wString( const ON_wString& ); + enum : int + { + // This ON_String::MaximumStringLength value of 100,000,000 + // is used for string length sanity checking in both ON_String and + // ON_wString. + // The design of the ON_String and ON_wString classes could support + // string lengths up to 0xFFFFFFFEU = 4,294,967,294 but + // a cap of 100 million seems generous enough for current usage + // and is small enough to detect many corrupt memory + // situations. + // This value is used for both ON_String and ON_wString. + MaximumStringLength = ON_String::MaximumStringLength + }; + #if defined(ON_HAS_RVALUEREF) // Clone constructor ON_wString( ON_wString&& ) ON_NOEXCEPT; @@ -2956,6 +3497,207 @@ public: ON_wString Reverse() const; + /* + Description: + Convert a literal string into an XML encoded value. + + < (less-than) is replaced with < + > (greater-than) is replaced with > + & (ampersand) is replaces with & + ' (apostrophe or single quote) is replaced with ' + " (double-quote) is replaced with " + + Optionally, unocode code points U+hhhh where hhhh >= 0x80 are replaced + with &#xhhhh; using the minimal number of hex digits. + Parameters: + bUseUnicodeCodePointsForSpecialCharacters - [in] + If true, the <, >, &, ', and " encodings are used. + If false, the < > &, ', and " encodings are used. + When in doubt, pass false. + bEncodeCodePointsAboveBasicLatin + If true, any code point >= 0x80 is encoded using the XML &xhhhh; format. + When human readability is important and the XML will be parsed by a + high quality XML reader, pass false. (The XMLspecification supports text + files that are UTF=8, UTF-18, or UTF-32 encoded.) + Returns: + A string with every instance of an xml special character replaced + with its xml encoding and, optionally, every code point > 127 replaced + with &xhhhh;. + */ + const ON_wString EncodeXMLValue() const; + + const ON_wString EncodeXMLValue( + bool bEncodeCodePointsAboveBasicLatin + ) const; + + /* + Description: + Decode an XML encoded value. + Examples: + < is replaced with < (less-than). + > is replaced with > (greater-than). + & is replaced with & (ampersand). + ' is replaced with ' (apostrophe or single quote). + " is replaced with " (double-quote). + &#nnnn; where nnnn is a valid decimal unicode code point is replaced with the wide character encode code point. + &#xhhhh; where hhhh is a valid hexadecimal unicode code point is replaced with the wide character encode code point. + Returns: + This string with every instance of an xml encodecharacter encodeding replaced + with the corresponding wide character encodeing of the literal unicode code point. + */ + const ON_wString DecodeXMLValue() const; + + /* + Description: + Parse one of the following XML chacater encodings. + &#nnnn; (nnnn = one of more decimal digits) is parsed to the unicode code point with decimal value nnnn + &#xhhhh; (nnnn = one of more hexadecimal digits) is parsed to the unicode code point with hexadecimal value hhhh + < is parsed to < (less-than). + > is parsed to > (greater-than). + & is parsed to & (ampersand). + ' is parsed to ' (apostrophe or single quote). + " is parsed to " (double-quote). + Parameters: + buffer - [in] + buffer to parse. The first character of buffer[] must be the leading ampersand. + The buffer must include the terminating semicolon. + buffer_length - [in] + If -1, then buffer[] must be null terminated. + Otherwise buffer_length specifies the number of whcar_t elements + that may be parsed. + value_on_failure - [in] + *unicode_code_point is set to value_on_failure if parsing fails. + unicode_code_point - [out] + parsed unicode code point. If you do not want the code point, you may pass nullptr. + Returns: + If parsing is successful, the first element of buffer that was not parsed is returned. + Otherwise nullptr is returned. + Remarks: + Note that the XML 1 (section 2.2 of the WC3 specification) does not permit surrogate pair encodings. + */ + static const wchar_t* ParseXMLCharacterEncoding( + const wchar_t* buffer, + int buffer_length, + unsigned value_on_failure, + unsigned* unicode_code_point + ); + + + /* + Description: + Parse an xml encoded unicde code point. + &#nnnn; (nnnn = any number of decimal digits) + &#xhhhh; (hhhh = any muber of hexadecimal digits) + Parameters: + buffer - [in] + buffer to parse. + The first character of buffer[] must be the leading ampersand. + The second character of buffer[] must be the number sign. + The buffer must include the terminating semicolon. + buffer_length - [in] + If -1, then buffer[] must be null terminated. + Otherwise buffer_length specifies the number of whcar_t elements + that may be parsed. + value_on_failure - [in] + *unicode_code_point is set to value_on_failure if parsing fails. + unicode_code_point - [out] + parsed unicode code point. If you do not want the code point, you may pass nullptr. + Returns: + If parsing is successful, the first element of buffer that was not parsed is returned. + Otherwise nullptr is returned. + */ + static const wchar_t* ParseXMLUnicodeCodePointEncoding( + const wchar_t* buffer, + int buffer_length, + unsigned value_on_failure, + unsigned* unicode_code_point + ); + + /* + Description: + Parse over horizontal space in the string. + Parameters: + s - [in] + string to parse. + len - [in] + maximum number of characters to parse. + You many pass -1 if s is null terminated. + bParseTab - [in] + True if a horizontal tab should be treated as horizontal space. + bParseNoBreakSpace - [in] + True if no break space code points should be treated as horizontal space. + bParseZeroWidthSpace - [in] + True if zero width code points should be treated as horizontal space. + Returns: + If horizontal spaces were successfully parsed, first character after + the horizontal spaces is returned. Otherwise s is returned. + */ + static const wchar_t* ParseHorizontalSpace( + const wchar_t* s, + int len, + bool bParseTab, + bool bParseNoBreakSpace, + bool bParseZeroWidthSpace + ); + + /* + Description: + Parse over horizontal space in the string. + This version of ParseHorizontalSpace() treas tabs and zero width code points + as horizontal space. If you need more nuanced control, call the override + with bool parameters. + Parameters: + s - [in] + string to parse. + len - [in] + maximum number of characters to parse. + You many pass -1 if s is null terminated. + Returns: + If horizontal spaces were successfully parsed, first character after + the horizontal spaces is returned. Otherwise s is returned. + */ + static const wchar_t* ParseHorizontalSpace( + const wchar_t* s, + int len + ); + + /* + Description: + Parse the string s as a vulgar fraction (1/2). + Parameters: + s - [in] + string to parse. + s[0] must be a sign or a digit. It can be the ordinary characters or superscripts. + If the first digit is an ordinary digit, the the numerator and denominator must all + be ordinary digits. + If the first digit is a superscript digit, the the numerator must be all superscript + digits and the denominator be all subscript digits. + len - [in] + maximum number of characters to parse. + You many pass -1 if s is null terminated. + numerator - [out] + denominator - [out] + Returns: + If a vulgar fraction was succesfully parsed, the a pointer to the first character + after the vulgar fraction is returned. Otherwise nullptr is returned. + */ + static const wchar_t* ParseVulgarFraction( + const wchar_t* s, + int len, + int& numerator, + int& denomintor + ); + + /// + /// Returns true if c is one of the 5 XML special characters + /// & (ampersand), + /// < (less than), + /// > (greater than), + /// " (quotation mark), or + /// ' (apostrophe). + /// + static bool IsXMLSpecialCharacter(wchar_t c); + static wchar_t* Reverse( wchar_t* string, int element_count @@ -3357,6 +4099,10 @@ public: ON__UINT32 error_code_point ); + static const ON_wString FromUnicodeCodePoint( + ON__UINT32 code_point + ); + /* Description: Each byte value is converted to 2 hexadecimal digits. @@ -3515,6 +4261,116 @@ public: va_list args ); + /* + Returns: + A Unicode encoding of the fraction numerator/denominator with the minimum number of characters. + The fraction is reduced and the encoding is a short as possible. + */ + static const ON_wString FormatToVulgarFraction(int numerator, int denominator); + + /* + Returns: + A Unicode encoding of the fraction numerator/denominator with the minimum number of characters. + bReduced - [in] + When in doubt, pass true. + If true, then the reduced fraction will be returned. + For example, if bReduced is true, then 2/4 reduces to 1/2. + bProper - [in] + When in doubt, pass true. + If true, then proper fractions will be returned when abs(numerator)>=abs(denominator). + For example, if bProper is true, then 4/3 is converted to 1-1/3. + The symbol between the whole number and the proper fraction is specified by + mixed_fraction_separator_code_point. + proper_fraction_separator_cp - [in] + Species the Unicode code point of the symbol used to + separate the whole number and the proper fraction. + + When in doubt, pass ON_UnicodeCodePoint::ON_NullCodePoint (0) which will result in the + large whole number being next to the proper fraction in a readable and compact manner. + + Other options include: + + Spaces: + ON_UnicodeCodePoint::ON_NarrowNoBreakSpace + ON_UnicodeCodePoint::ON_NoBreakSpace + ON_UnicodeCodePoint::ON_ZeroWidthSpace + ON_UnicodeCodePoint::ON_Space + ON_UnicodeCodePoint::ON_EnSpace + ON_UnicodeCodePoint::ON_EmSpace + ON_UnicodeCodePoint::ON_FigureSpace + ON_UnicodeCodePoint::ON_MediumMathematicalSpace + ON_UnicodeCodePoint::ON_ThinSpace + ON_UnicodeCodePoint::ON_HairSpace + ON_UnicodeCodePoint::ON_PunctuationSpace + ON_UnicodeCodePoint::ON_ThreePerEmSpace + ON_UnicodeCodePoint::ON_FourPerEmSpace + ON_UnicodeCodePoint::ON_SixPerEmSpace + + Hyphens: + ON_UnicodeCodePoint::ON_HyphenMinus + ON_UnicodeCodePoint::ON_UnambiguousHyphen + ON_UnicodeCodePoint::ON_NoBreakHyphen + ON_UnicodeCodePoint::ON_SmallHyphen + + Dashes: + ON_UnicodeCodePoint::ON_FigureDash + ON_UnicodeCodePoint::ON_EnDash + ON_UnicodeCodePoint::ON_EmDash + + bUseVulgarFractionCodePoints - [in] + When in doubt, pass true. + If true and a single Unicode code point exists for the vulgar fraction + (1/2, 1/3, 2/3, 1/4, 3/4, 1/5, 2/5, 3/5, 4/5, 1/6, 5/6, 1/7, 1/8, 3/8, 5/8, 7/8, 1/9, 1/10), + then that code point will be used. + Otherwise a Unicode superscript digits, ON_UnicodeCodePoint::FractionSlash, and Unicode subscript digits + are used. + */ + static const ON_wString FormatToVulgarFraction( + int numerator, + int denominator, + bool bReduced, + bool bProper, + unsigned int proper_fraction_separator_cp, + bool bUseVulgarFractionCodePoints + ); + + /* + numerator - [in] + A string (digits, signs, parenthesis). + denominator - [in] + A string (digits, signs, parenthesis). + bUseVulgarFractionCodePoints - [in] + If true and if Unicode code point exists for the fraction (halves, thirds, fourths, fifths, sixths, eights, ...), + that code point will be used; + Returns: + A Unicode encoding of the fraction numerator/denominator. + */ + static const ON_wString FormatToVulgarFraction( + const ON_wString numerator, + const ON_wString denominator + ); + + /* + numerator - [in] + A string (digits, signs, parenthesis). + Returns: + A Unicode encoding of the fraction's numerator. + */ + static const ON_wString FormatToVulgarFractionNumerator(const ON_wString numerator); + + /* + denominator - [in] + A string (digits, signs, parenthesis). + Returns: + A Unicode encoding of the fraction's denominator. + */ + static const ON_wString FormatToVulgarFractionDenominator(const ON_wString denominator); + + /* + Returns a string with the code point U+2044. + */ + static const ON_wString VulgarFractionSlash(); + /* Parameters: format - [in] @@ -3543,6 +4399,9 @@ public: ); /* + Description: + Parses buffer to extract a number. + Sperscript and supscript numbers are supported. Returns: not zero: pointer to the first character that was not scanned @@ -3595,6 +4454,18 @@ public: double* value ); + /* + Parameters: + sz - [in] + number of bytes. + Returns: + If sz < 0, "0 bytes" is returned. + If 0 <= sz <= 9999, "x bytes" is returned where x is an exact decimal value. + If s > 9999, then a description with 3 significant digits and a suffix indicating + the order of magnitude is returned. The order of magnitude is described by appending + KB (1024 bytes), MB (1024 KB), GB (1024 MB), TB (1024 GB), or PB (1024 TB). + */ + static const ON_wString ToMemorySize(size_t sz); /* Description: @@ -3613,6 +4484,13 @@ public: wchar_t* Array(); const wchar_t* Array() const; + /* + Returns: + A duplicate of this that does not share memory with any other string. + (A new array is allocated for the returned string.) + */ + const ON_wString Duplicate() const; + /* Returns: Total number of bytes of memory used by this class. diff --git a/opennurbs_string_scan.cpp b/opennurbs_string_scan.cpp index eff4b506..34df9069 100644 --- a/opennurbs_string_scan.cpp +++ b/opennurbs_string_scan.cpp @@ -492,33 +492,46 @@ const wchar_t* ON_wString::ToNumber( if (nullptr == value) return nullptr; - ON__INT64 i; - ON__UINT64 u; - const wchar_t* rc; - if ('-' == buffer[0] && buffer[1] >= '0' && buffer[1] <= '9') + ON__INT64 i = value_on_failure; + ON__UINT64 u = 0; + const wchar_t* rc = nullptr; + + const wchar_t c0 = buffer[0]; + const int sign = ON_wString::PlusOrMinusSignFromWideChar(c0, true, true, true); + if (0 != sign) + ++buffer; // c0 is some type of plus or minus sign. + + const bool b0 = ON_wString::IsDecimalDigit(buffer[0], true, false, false); + const bool b1 = false == b0 && ON_wString::IsDecimalDigit(buffer[0], false, true, false); + const bool b2 = false == b0 && false == b1 && ON_wString::IsDecimalDigit(buffer[0], false, false, true); + + if ((b0 || b1 || b2) && sign == ON_wString::PlusOrMinusSignFromWideChar(c0, b0, b1, b2)) { - rc = ON_wString::ToNumber(buffer + 1, 0, &u); - if (nullptr != rc && u <= 9223372036854775808LLU) + if (sign < 0) { - i = -((ON__INT64)u); + rc = ON_wString::ToNumber(buffer, 0, &u); + if (nullptr != rc && u <= 9223372036854775808LLU) + { + i = -((ON__INT64)u); + } + else + { + i = value_on_failure; + rc = nullptr; + } } else { - i = value_on_failure; - rc = nullptr; - } - } - else - { - rc = ON_wString::ToNumber(buffer, 0, &u); - if (nullptr != rc && u <= 9223372036854775807LLU) - { - i = (ON__INT64)u; - } - else - { - i = value_on_failure; - rc = nullptr; + rc = ON_wString::ToNumber(buffer, 0, &u); + if (nullptr != rc && u <= 9223372036854775807LLU) + { + i = (ON__INT64)u; + } + else + { + i = value_on_failure; + rc = nullptr; + } } } @@ -540,17 +553,24 @@ const wchar_t* ON_wString::ToNumber( if (nullptr != buffer) { - if ('+' == buffer[0]) - buffer++; - if (buffer[0] >= '0' && buffer[0] <= '9') + const wchar_t c0 = buffer[0]; + const int sign = ON_wString::PlusOrMinusSignFromWideChar(c0,true,true,true); + if (sign > 0) + ++buffer; // c0 is some type of plus sign. + + const bool b0 = ON_wString::IsDecimalDigit(buffer[0], true, false, false); + const bool b1 = false == b0 && ON_wString::IsDecimalDigit(buffer[0], false, true, false); + const bool b2 = false == b0 && false == b1 && ON_wString::IsDecimalDigit(buffer[0], false, false, true); + + if ((b0 || b1 || b2) && sign == ON_wString::PlusOrMinusSignFromWideChar(c0, b0, b1, b2)) { - ON__UINT64 r = (ON__UINT64)(*buffer++ - '0'); + ON__UINT64 r = 0; for (const wchar_t* s = buffer;/*empty test*/; s++) { - if (*s >= '0' && *s <= '9') + const ON__UINT64 d = (ON__UINT64)ON_wString::DecimalDigitFromWideChar(*s, b0, b1, b2, 10); + if (d < 10LLU) { - ON__UINT64 d = ON__UINT64(*s - '0'); - ON__UINT64 r1 = r * 10LLU + d; + const ON__UINT64 r1 = r * 10LLU + d; if (r1 < r) { // overflow @@ -665,5 +685,70 @@ const wchar_t* ON_wString::ToNumber( return nullptr; } +const ON_wString ON_wString::ToMemorySize(size_t size_in_bytes) +{ + if (size_in_bytes <= 0) + return ON_wString(L"0 bytes"); + + ON__UINT64 sz = (ON__UINT64)size_in_bytes; + + const wchar_t* units; + const ON__UINT64 kb = 1024; + const ON__UINT64 mb = kb * kb; + const ON__UINT64 gb = kb * mb; + const ON__UINT64 tb = kb * gb; + const ON__UINT64 pb = kb * tb; + if (sz >= pb) + { + // petabytes + sz /= tb; + units = L"PB"; + } + else if (sz >= tb) + { + // terabytes + sz /= gb; + units = L"TB"; + } + else if (sz >= gb) + { + // gigabytes + sz /= mb; + units = L"GB"; + } + else if (sz >= mb) + { + // megaabytes + sz /= kb; + units = L"MB"; + } + else if (sz >= kb) + { + // kilobytes + units = L"KB"; + } + else + { + // bytes + return (1==sz) ? ON_wString(L"1 byte") : ON_wString::FormatToString(L"%u bytes",(unsigned)sz); + } + + const ON__UINT64 n = sz / kb; + const ON__UINT64 r = sz % kb; + if (r > 0 && n < 100) + { + const double x = ((double)sz)/((double)kb); + if (0 == n) + return ON_wString::FormatToString(L"0.03f %ls", x, units); + + if (n >= 10) + return ON_wString::FormatToString(L"%0.1f %ls", x, units); + + return ON_wString::FormatToString(L"%0.2f %ls", x, units); + } + + return ON_wString::FormatToString(L"%u %ls", ((unsigned)n), units); +} + #undef SIGNED_TO_NUMBER #undef UNSIGNED_TO_NUMBER diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index 36455454..a9cf6c3e 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -558,6 +558,12 @@ bool ON_SubDVertexPtr::IsNotNull() const return (nullptr != ON_SUBD_VERTEX_POINTER(m_ptr)); } +unsigned int ON_SubDVertexPtr::VertexId() const +{ + const ON_SubDVertex* v = ON_SUBD_VERTEX_POINTER(m_ptr); + return (nullptr != v) ? v->m_id : 0U; +} + class ON_SubDVertex* ON_SubDVertexPtr::Vertex() const { return ON_SUBD_VERTEX_POINTER(m_ptr); @@ -684,6 +690,25 @@ const class ON_SubDVertex* ON_SubDEdgePtr::RelativeVertex( return nullptr; } +bool ON_SubDEdgePtr::RelativeVertexMark( + int relative_vertex_index, + bool missing_vertex_return_value +) const +{ + const ON_SubDVertex* v = this->RelativeVertex(relative_vertex_index); + return (nullptr != v) ? v->Mark() : missing_vertex_return_value; +} + +ON__UINT8 ON_SubDEdgePtr::RelativeVertexMarkBits( + int relative_vertex_index, + ON__UINT8 missing_vertex_return_value +) const +{ + const ON_SubDVertex* v = this->RelativeVertex(relative_vertex_index); + return (nullptr != v) ? v->MarkBits() : missing_vertex_return_value; +} + + const ON_3dPoint ON_SubDEdgePtr::RelativeControlNetPoint( int relative_vertex_index ) const @@ -915,22 +940,12 @@ int ON_SubDFacePtr::CompareFacePointer( bool ON_SubDComponentPtr::IsNull() const { - return (0 == (ON_SUBD_COMPONENT_POINTER_MASK && m_ptr)); + return nullptr == ComponentBase(); } bool ON_SubDComponentPtr::IsNotNull() const { - if (nullptr != ON_SUBD_COMPONENT_POINTER(m_ptr)) - { - switch (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr) - { - case ON_SUBD_COMPONENT_TYPE_VERTEX: - case ON_SUBD_COMPONENT_TYPE_EDGE: - case ON_SUBD_COMPONENT_TYPE_FACE: - return true; - } - } - return false; + return nullptr != ComponentBase(); } unsigned int ON_SubDComponentPtr::ComponentId() const @@ -1154,6 +1169,174 @@ unsigned int ON_SubDComponentPtr::ClearStates( return ON_SUBD_RETURN_ERROR(0); } +ON_SubDComponentTest::ON_SubDComponentTest(ON__UINT_PTR ptr) +: m_ptr(ptr) +{} + +ON_SubDComponentTest::~ON_SubDComponentTest() +{} + +bool ON_SubDComponentTest::Passes(const ON_SubDComponentPtr cptr) const +{ + // Default implementation of a virtual function that is typically overridden + return cptr.IsNotNull() && 0 != m_ptr; +} + +bool ON_SubDComponentTest::Passes(const class ON_SubDVertex* v) const +{ + return Passes((nullptr != v) ? v->ComponentPtr() : ON_SubDComponentPtr::Null); +} + +bool ON_SubDComponentTest::Passes(const class ON_SubDEdge* e) const +{ + return Passes((nullptr != e) ? e->ComponentPtr() : ON_SubDComponentPtr::Null); +} + +bool ON_SubDComponentTest::Passes(const ON_SubDFace* f) const +{ + return Passes((nullptr != f) ? f->ComponentPtr() : ON_SubDComponentPtr::Null); +} + +ON_SubDComponentId::ON_SubDComponentId(ON_SubDComponentPtr::Type component_type, unsigned int component_id) + : m_id(component_id) + , m_type(component_type) +{} + +ON_SubDComponentId::ON_SubDComponentId(ON_SubDComponentPtr cptr) +{ + const ON_SubDComponentBase* b = cptr.ComponentBase(); + if (nullptr != b) + { + m_id = b->m_id; + m_type = cptr.ComponentType(); + } +} + +ON_SubDComponentId::ON_SubDComponentId(const class ON_SubDVertex* v) +{ + if (nullptr != v) + { + m_id = v->m_id; + m_type = ON_SubDComponentPtr::Type::Vertex; + } +} + +ON_SubDComponentId::ON_SubDComponentId(const class ON_SubDEdge* e) +{ + if (nullptr != e) + { + m_id = e->m_id; + m_type = ON_SubDComponentPtr::Type::Edge; + } +} + +ON_SubDComponentId::ON_SubDComponentId(const class ON_SubDFace* f) +{ + if (nullptr != f) + { + m_id = f->m_id; + m_type = ON_SubDComponentPtr::Type::Face; + } +} + + +int ON_SubDComponentId::CompareTypeAndId(const ON_SubDComponentId& lhs, const ON_SubDComponentId& rhs) +{ + if (static_cast(lhs.m_type) < static_cast(rhs.m_type)) + return -1; + if (static_cast(lhs.m_type) > static_cast(rhs.m_type)) + return 1; + if (lhs.m_id < rhs.m_id) + return -1; + if (lhs.m_id > rhs.m_id) + return 1; + return 0; +} + +int ON_SubDComponentId::CompareTypeAndIdFromPointer(const ON_SubDComponentId* lhs, const ON_SubDComponentId* rhs) +{ + if (lhs == rhs) + return 0; + + // nullptr sorts to end of list + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + + if (static_cast(lhs->m_type) < static_cast(rhs->m_type)) + return -1; + if (static_cast(lhs->m_type) > static_cast(rhs->m_type)) + return 1; + if (lhs->m_id < rhs->m_id) + return -1; + if (lhs->m_id > rhs->m_id) + return 1; + return 0; +} + +unsigned int ON_SubDComponentId::ComponentId() const +{ + return m_id; +} + +ON_SubDComponentPtr::Type ON_SubDComponentId::ComponentType() const +{ + return m_type; +} + +bool ON_SubDComponentId::IsSet() const +{ + return 0 != m_id && ON_SubDComponentPtr::Type::Unset != m_type; +} + +bool ON_SubDComponentIdList::Passes(const ON_SubDComponentPtr cptr) const +{ + return InList(cptr) ? m_bInListPassesResult : !m_bInListPassesResult; +} + +void ON_SubDComponentIdList::AddId(ON_SubDComponentId cid) +{ + if (cid.IsSet()) + { + m_bSorted = false; + m_id_list.Append(cid); + } +} + +void ON_SubDComponentIdList::AddId(ON_SubDComponentPtr cptr) +{ + AddId(ON_SubDComponentId(cptr)); +} + +void ON_SubDComponentIdList::SetInListPassesResult(bool bInListPassesResult) +{ + m_bInListPassesResult = bInListPassesResult ? true : false; +} + +bool ON_SubDComponentIdList::InListPassesResult() const +{ + return m_bInListPassesResult; +} + +bool ON_SubDComponentIdList::InList(ON_SubDComponentId cid) const +{ + if (false == m_bSorted) + { + m_bSorted = true; + m_id_list.QuickSortAndRemoveDuplicates(ON_SubDComponentId::CompareTypeAndIdFromPointer); + } + const bool bInList = m_id_list.BinarySearch(&cid, ON_SubDComponentId::CompareTypeAndIdFromPointer) >= 0; + + return bInList; +} + +bool ON_SubDComponentIdList::InList(ON_SubDComponentPtr cptr) const +{ + return InList(ON_SubDComponentId(cptr)); +} + + bool ON_SubDComponentPtr::Mark() const { const ON_SubDComponentBase* c = this->ComponentBase(); @@ -1423,12 +1606,31 @@ class ON_SubDComponentBase* ON_SubDComponentPtr::ComponentBase() const case ON_SUBD_COMPONENT_TYPE_VERTEX: case ON_SUBD_COMPONENT_TYPE_EDGE: case ON_SUBD_COMPONENT_TYPE_FACE: + // During archive id mapping, the returned value can be an archive id and not a true pointer. + // This is in a controlled setting inside functions like ON_SubDArchiveIdMap::ConvertArchiveIdToRuntimeSymmetrySetNextPtr(). + // All public level SDK code can safely assume the returned value is a true pointer. + // It does mean that you cannot "validate" the value returned here + // using some contraint on what you feel is a reasonable true pointer value. return ((class ON_SubDComponentBase*)ON_SUBD_COMPONENT_POINTER(m_ptr)); + break; } return nullptr; } +class ON_SubDComponentBase* ON_SubDComponentPtr::ComponentBase(ON_SubDComponentPtr::Type type_filter) const +{ + const ON_SubDComponentPtr::Type ptr_type = static_cast((unsigned char)(ON_SUBD_COMPONENT_TYPE_MASK & m_ptr)); + switch (ptr_type) + { + case ON_SubDComponentPtr::Type::Vertex: + case ON_SubDComponentPtr::Type::Edge: + case ON_SubDComponentPtr::Type::Face: + return (ptr_type == type_filter || ON_SubDComponentPtr::Type::Unset == type_filter) ? ((class ON_SubDComponentBase*)ON_SUBD_COMPONENT_POINTER(m_ptr)) : nullptr; + } + return nullptr; +} + class ON_SubDVertex* ON_SubDComponentPtr::Vertex() const { if (ON_SUBD_COMPONENT_TYPE_VERTEX == (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr)) @@ -2004,6 +2206,230 @@ bool ON_SubDComponentPtrPair::BothAreNotNull() const return m_pair[0].IsNotNull() && m_pair[1].IsNotNull(); } + +///////////////////////////////////////////////////////////////////////// +// +// ON_SubD_ComponentIdTypeAndTag +// + +const ON_wString ON_SubD_ComponentIdTypeAndTag::ToString() const +{ + switch (m_type) + { + case ON_SubDComponentPtr::Type::Unset: + break; + case ON_SubDComponentPtr::Type::Vertex: + return ON_wString::FormatToString(L"Vertex id=%u tag=",m_id) + ON_SubD::VertexTagToString(VertexTag(),false); + break; + case ON_SubDComponentPtr::Type::Edge: + return ON_wString::FormatToString(L"Edge id=%u tag=",m_id) + ON_SubD::EdgeTagToString(EdgeTag(), false); + break; + case ON_SubDComponentPtr::Type::Face: + return ON_wString::FormatToString(L"Face id=%u tag=%u", m_id, (unsigned)FaceTag()); + break; + default: + break; + } + return ON_wString(L"Unset"); +} + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromVertex(const ON_SubDVertex* v) +{ + ON_SubD_ComponentIdTypeAndTag itt + = (nullptr != v) + ? ON_SubD_ComponentIdTypeAndTag::CreateFromVertexId(v->m_id, v->m_vertex_tag) + : ON_SubD_ComponentIdTypeAndTag::Unset; + if (itt.m_id > 0) + itt.m_cptr = ON_SubDComponentPtr::Create(v); + return itt; +} + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromVertexId(unsigned vertex_id, ON_SubDVertexTag vtag) +{ + ON_SubD_ComponentIdTypeAndTag itt; + if (vertex_id > 0) + { + itt.m_id = vertex_id; + itt.m_type = ON_SubDComponentPtr::Type::Vertex; + itt.m_tag = static_cast(vtag); + } + return itt; +} + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromEdge(const ON_SubDEdge * e) +{ + ON_SubD_ComponentIdTypeAndTag itt + = (nullptr != e) + ? ON_SubD_ComponentIdTypeAndTag::CreateFromEdgeId(e->m_id, e->m_edge_tag) + : ON_SubD_ComponentIdTypeAndTag::Unset; + if (itt.m_id > 0) + itt.m_cptr = ON_SubDComponentPtr::Create(e); + return itt; +} + + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromEdgeId(unsigned edge_id, ON_SubDEdgeTag etag) +{ + ON_SubD_ComponentIdTypeAndTag itt; + if (edge_id > 0) + { + itt.m_id = edge_id; + itt.m_type = ON_SubDComponentPtr::Type::Edge; + itt.m_tag = static_cast(ON_SubDEdgeTag::SmoothX == etag ? ON_SubDEdgeTag::Smooth : etag); + } + return itt; +} + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromFace(const ON_SubDFace* f, unsigned char ftag) +{ + ON_SubD_ComponentIdTypeAndTag itt + = (nullptr != f) + ? ON_SubD_ComponentIdTypeAndTag::CreateFromFaceId(f->m_id, ftag) + : ON_SubD_ComponentIdTypeAndTag::Unset; + if (itt.m_id > 0) + itt.m_cptr = ON_SubDComponentPtr::Create(f); + return itt; +} + +const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::CreateFromFaceId(unsigned face_id, unsigned char ftag) +{ + ON_SubD_ComponentIdTypeAndTag itt; + if (face_id > 0) + { + itt.m_id = face_id; + itt.m_type = ON_SubDComponentPtr::Type::Vertex; + itt.m_tag = ftag; + } + return itt; +} + +int ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(const ON_SubD_ComponentIdTypeAndTag * lhs, const ON_SubD_ComponentIdTypeAndTag * rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->m_type < rhs->m_type) + return -1; + if (lhs->m_type > rhs->m_type) + return 1; + if (lhs->m_id < rhs->m_id) + return -1; + if (lhs->m_id > rhs->m_id) + return 1; + return 0; +} + +int ON_SubD_ComponentIdTypeAndTag::CompareTypeAndIdAndTag(const ON_SubD_ComponentIdTypeAndTag* lhs, const ON_SubD_ComponentIdTypeAndTag* rhs) +{ + int rc = ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(lhs, rhs); + if (0 != rc) + return rc; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (lhs->m_tag < rhs->m_tag) + return -1; + if (lhs->m_tag > rhs->m_tag) + return 1; + return 0; +} + +ON_SubDVertexTag ON_SubD_ComponentIdTypeAndTag::OriginalVertexTag(unsigned vertex_id, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags) +{ + if (0 == vertex_id) + return ON_SubDVertexTag::Unset; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromVertexId(vertex_id,ON_SubDVertexTag::Unset); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + return (i >= 0) ? sorted_tags[i].VertexTag() : ON_SubDVertexTag::Unset; +} + +ON_SubDVertexTag ON_SubD_ComponentIdTypeAndTag::OriginalVertexTag(const ON_SubDVertex* v, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags) +{ + if (nullptr == v) + return ON_SubDVertexTag::Unset; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromVertexId(v->m_id,ON_SubDVertexTag::Unset); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + return (i >= 0) ? sorted_tags[i].VertexTag() : v->m_vertex_tag; +} + +ON_SubDEdgeTag ON_SubD_ComponentIdTypeAndTag::OriginalEdgeTag(unsigned edge_id, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags) +{ + if (0 == edge_id) + return ON_SubDEdgeTag::Unset; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromEdgeId(edge_id, ON_SubDEdgeTag::Unset); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + return (i >= 0) ? sorted_tags[i].EdgeTag() : ON_SubDEdgeTag::Unset; +} + + +ON_SubDEdgeTag ON_SubD_ComponentIdTypeAndTag::OriginalEdgeTag(const ON_SubDEdge * e, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>&sorted_tags) +{ + if (nullptr == e) + return ON_SubDEdgeTag::Unset; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromEdgeId(e->m_id,ON_SubDEdgeTag::Unset); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + return (i >= 0) ? sorted_tags[i].EdgeTag() : e->m_edge_tag; +} + +unsigned char ON_SubD_ComponentIdTypeAndTag::OriginalFaceTag(unsigned face_id, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags) +{ + if (0 == face_id) + return 0; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromFaceId(face_id, 0); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + return (i >= 0) ? sorted_tags[i].FaceTag() : 0; +} + + +unsigned char ON_SubD_ComponentIdTypeAndTag::OriginalFaceTag(const ON_SubDFace* f, const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags) +{ + if (nullptr == f) + return 0; + const ON_SubD_ComponentIdTypeAndTag itt = ON_SubD_ComponentIdTypeAndTag::CreateFromFaceId(f->m_id,0); + const int i = sorted_tags.BinarySearch(&itt, ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId); + const unsigned char ftag = (i >= 0) ? sorted_tags[i].FaceTag() : 0; + return ftag; +} + +ON_SubDComponentPtr::Type ON_SubD_ComponentIdTypeAndTag::ComponentType() const +{ + return m_type; +} + +unsigned ON_SubD_ComponentIdTypeAndTag::VertexId() const +{ + return (ON_SubDComponentPtr::Type::Vertex == m_type) ? m_id : 0; +} + +unsigned ON_SubD_ComponentIdTypeAndTag::EdgeId() const +{ + return (ON_SubDComponentPtr::Type::Edge == m_type) ? m_id : 0; +} + +unsigned ON_SubD_ComponentIdTypeAndTag::FaceId() const +{ + return (ON_SubDComponentPtr::Type::Face == m_type) ? m_id : 0; +} + +ON_SubDVertexTag ON_SubD_ComponentIdTypeAndTag::VertexTag() const +{ + return (ON_SubDComponentPtr::Type::Vertex == m_type) ? ON_SubD::VertexTagFromUnsigned(m_tag) : ON_SubDVertexTag::Unset; +} + +ON_SubDEdgeTag ON_SubD_ComponentIdTypeAndTag::EdgeTag() const +{ + return (ON_SubDComponentPtr::Type::Edge == m_type) ? ON_SubD::EdgeTagFromUnsigned(m_tag) : ON_SubDEdgeTag::Unset; +} + +unsigned char ON_SubD_ComponentIdTypeAndTag::FaceTag() const +{ + return (ON_SubDComponentPtr::Type::Face == m_type) ? m_tag : 0; +} + ////////////////////////////////////////////////////////////////////////// // // ON_SubDFromMeshParameters @@ -2983,6 +3409,11 @@ const ON_SubDEdgePtr ON_SubDVertex::CreasedEdge(bool bInteriorEdgesOnly) const return creased_eptr; } +const unsigned int ON_SubDVertex::CreasedEdgeCount() const +{ + return CreasedEdgeCount(true, true, true, true); +} + const unsigned int ON_SubDVertex::CreasedEdgeCount( bool bCountInteriorCreases, bool bCountBoundaryCreases, @@ -3413,7 +3844,8 @@ int ON_SubDVertex::CompareUnorderedEdgesAndFaces( // void ON_SubDComponentBase::CopyBaseFrom( - const ON_SubDComponentBase* src + const ON_SubDComponentBase* src, + bool bCopySymmetrySetNext ) { if ( nullptr == src ) @@ -3422,19 +3854,22 @@ void ON_SubDComponentBase::CopyBaseFrom( *this = *src; m_subd_point1 = nullptr; Internal_ClearSurfacePointFlag(); + if (bCopySymmetrySetNext) + m_symmetry_set_next = src->m_symmetry_set_next; } void ON_SubDEdge::CopyFrom( const ON_SubDEdge* src, bool bReverseEdge, bool bCopyVertexArray, - bool bCopyFaceArray - ) + bool bCopyFaceArray, + bool bCopySymmetrySetNext +) { if (nullptr == src) src = &ON_SubDEdge::Empty; - CopyBaseFrom(src); + CopyBaseFrom(src, bCopySymmetrySetNext); m_next_edge = nullptr; @@ -3838,6 +4273,92 @@ const ON_SubDEdge* ON_SubDEdge::FromVertices( return e; } +const ON_SubDFacePtr ON_SubDFace::FromVertices(const ON_SimpleArray< const ON_SubDVertex* >& vertex_list) +{ + return ON_SubDFace::FromVertices(vertex_list.Array(), vertex_list.UnsignedCount()); +} + +const ON_SubDFacePtr ON_SubDFace::FromVertices(const ON_SubDVertex* const* vertex_list, size_t vertex_count) +{ + if (nullptr == vertex_list || vertex_count < 3 || vertex_count > ON_SubDFace::MaximumEdgeCount) + return ON_SubDFacePtr::Null; + + const ON_SubDFace* candiates4[4]; + const ON_SubDFace** candidates = nullptr; + unsigned candidate_count = 0; + const ON_SubDFace* f = nullptr; + + const unsigned unsigned_vertex_count = (unsigned)vertex_count; + if (unsigned_vertex_count < 3) + return ON_SubDFacePtr::Null; + const ON_SubDVertex* v[2] = { nullptr,vertex_list[0] }; + for (unsigned fei = 0; fei < unsigned_vertex_count; ++fei) + { + v[0] = v[1]; + v[1] = vertex_list[(fei + 1) % unsigned_vertex_count]; + const ON_SubDEdge* e = ON_SubDEdge::FromVertices(v[0], v[1]).Edge(); + if (nullptr == e || e->m_face_count <= 0) + candidate_count = 0; + else if (0 == fei) + { + candidates = (e->m_face_count <= 4) ? candiates4 : ((const ON_SubDFace**)onmalloc(e->m_face_count * sizeof(candidates[0]))); + for (unsigned short efi = 0; efi < e->m_face_count; ++efi) + { + const ON_SubDFace* ef = e->Face(efi); + if (nullptr != ef && unsigned_vertex_count == ef->EdgeCount()) + candidates[candidate_count++] = ef; + } + } + else + { + unsigned c = 0; + for (unsigned i = 0; i < candidate_count; ++i) + { + if (e->FaceArrayIndex(candidates[i]) < ON_UNSET_UINT_INDEX) + candidates[c++] = candidates[i]; + } + candidate_count = c; + } + + if (0 == candidate_count) + break; + if (1 == candidate_count) + { + f = candidates[0]; + break; + } + } + + if (nullptr != candidates && candidates != candiates4) + onfree(candidates); + + if (nullptr == f) + return ON_SubDFacePtr::Null; + + const unsigned fvi0 = f->VertexIndex(vertex_list[0]); + if (fvi0 >= unsigned_vertex_count) + return ON_SubDFacePtr::Null; + + const ON__UINT_PTR dir = (vertex_list[1] == f->Vertex((fvi0 + 1) % unsigned_vertex_count)) ? 0 : 1; + if (0 == dir) + { + for (unsigned fvi = 2; fvi < unsigned_vertex_count; fvi++) + { + if (vertex_list[fvi] != f->Vertex((fvi0 + fvi) % unsigned_vertex_count)) + return ON_SubDFacePtr::Null; + } + } + else + { + for (unsigned fvi = 1; fvi < unsigned_vertex_count; fvi++) + { + if (vertex_list[fvi] != f->Vertex((fvi0 + unsigned_vertex_count - fvi) % unsigned_vertex_count)) + return ON_SubDFacePtr::Null; + } + } + return ON_SubDFacePtr::Create(f, dir); +} + const ON_3dPoint ON_SubDEdge::ControlNetPoint( unsigned int i) const { if (i >= 2 || nullptr == m_vertex[i]) @@ -3912,13 +4433,14 @@ const ON_3dVector ON_SubDEdge::ControlNetDirectionFrom( void ON_SubDFace::CopyFrom( const ON_SubDFace* src, - bool bCopyEdgeArray + bool bCopyEdgeArray, + bool bCopySymmetrySetNext ) { if (nullptr == src) src = &ON_SubDFace::Empty; - CopyBaseFrom(src); + CopyBaseFrom(src, bCopySymmetrySetNext); m_next_face = nullptr; @@ -3989,6 +4511,63 @@ unsigned int ON_SubDFace::EdgeCount() const return m_edge_count; } + +bool ON_SubDFace::HasEdges() const +{ + if (m_edge_count < 3 || m_edge_count > ON_SubDFace::MaximumEdgeCount) + return false; + if (m_edge_count > 4 + m_edgex_capacity) + return false; + const ON_SubDEdgePtr* eptr = m_edge4; + const ON_SubDVertex* v0 = nullptr; + const ON_SubDVertex* v1 = nullptr; + const ON_SubDVertex* ev[2]; + for (unsigned short fei = 0; fei < m_edge_count; ++fei, ++eptr) + { + if (4 == fei) + { + eptr = m_edgex; + if (nullptr == eptr) + return false; + if (m_edge_count > 4 + m_edgex_capacity) + return false; + } + const ON__UINT_PTR ptr = eptr->m_ptr; + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(ptr); + if (nullptr == e) + return false; + if (0 == e->m_face_count) + return false; + if (e->m_face_count > 2 + e->m_facex_capacity) + return false; + if (0 == ON_SUBD_EDGE_DIRECTION(ptr)) + { + ev[0] = e->m_vertex[0]; + ev[1] = e->m_vertex[1]; + } + else + { + ev[0] = e->m_vertex[1]; + ev[1] = e->m_vertex[0]; + } + if (nullptr == ev[0] || nullptr == ev[1] || ev[0] == ev[1]) + return false; + if (nullptr == v0) + v0 = ev[0]; + else if (v1 != ev[0]) + return false; + v1 = ev[1]; + if (v1->m_edge_count < 2 || v1->m_edge_count > v1->m_edge_capacity) + return false; + if (v1->m_face_count < 1 || v1->m_face_count > v1->m_face_capacity) + return false; + } + if ( v0 != v1) + return false; + + return true; +} + unsigned int ON_SubDFace::MarkedEdgeCount() const { unsigned int marked_edge_count = 0; @@ -4095,6 +4674,42 @@ unsigned int ON_SubDFace::MarkedVertexCount() const return marked_vertex_count; } +bool ON_SubDFace::EdgeMark( + unsigned int i, + bool bMissingEdgeReturnValue +) const +{ + const ON_SubDEdge* e = Edge(i); + return (nullptr != e) ? e->Mark() : bMissingEdgeReturnValue; +} + +ON__UINT8 ON_SubDFace::EdgeMarkBits( + unsigned int i, + ON__UINT8 missing_edge_markbits +) const +{ + const ON_SubDEdge* e = Edge(i); + return (nullptr != e) ? e->MarkBits() : missing_edge_markbits; +} + +bool ON_SubDFace::VertexMark( + unsigned int i, + bool bMissingVertexReturnValue +) const +{ + const ON_SubDVertex* v = Vertex(i); + return (nullptr != v) ? v->Mark() : bMissingVertexReturnValue; +} + +ON__UINT8 ON_SubDFace::VertexMarkBits( + unsigned int i, + ON__UINT8 missing_vertex_markbits +) const +{ + const ON_SubDVertex* v = Vertex(i); + return (nullptr != v) ? v->MarkBits() : missing_vertex_markbits; +} + const class ON_SubDVertex* ON_SubDFace::Vertex( unsigned int i ) const @@ -4315,6 +4930,31 @@ bool ON_SubDVertex::RemoveFaceFromArray(const ON_SubDFace * f) return true; } +bool ON_SubD::RemoveEdgeVertexConnection( + ON_SubDEdge* e, + ON_SubDVertex* v +) +{ + if (nullptr == e || nullptr == v) + return false; + if (v == e->m_vertex[0]) + e->m_vertex[0] = nullptr; + if (v == e->m_vertex[1]) + e->m_vertex[1] = nullptr; + return v->RemoveEdgeFromArray(e); +} + +ON_SubDVertex* ON_SubD::RemoveEdgeVertexConnection( + ON_SubDEdge* e, + unsigned evi +) +{ + if (nullptr == e) + return nullptr; + ON_SubDVertex* v = const_cast((nullptr != e && evi >= 0 && evi <= 1) ? e->m_vertex[evi] : nullptr); + return RemoveEdgeVertexConnection(e, v) ? v : nullptr; +} + ON_SubDVertexTag ON_SubDVertex::SuggestedVertexTag( bool bApplyInputTagBias, bool bReturnBestGuessWhenInvalid @@ -4810,57 +5450,79 @@ const ON_SubDHash ON_SubD::SubDHash( ) const { ON_SubDimple* subdimple = m_subdimple_sp.get(); - return (nullptr != subdimple) ? subdimple->SubDHash(hash_type,*this, bForceUpdate) : ON_SubDHash::Create(hash_type , *this); + return (nullptr != subdimple) ? subdimple->SubDHash(hash_type, bForceUpdate) : ON_SubDHash::Create(hash_type , *this); } +const ON_SHA1_Hash ON_SubD::GeometryHash() const +{ + return this->SubDHash(ON_SubDHashType::Geometry, false).SubDHash(); +} + +const ON_SHA1_Hash ON_SubD::TopologyHash() const +{ + return this->SubDHash(ON_SubDHashType::Topology, false).SubDHash(); +} const ON_SubDHash ON_SubDimple::SubDHash( ON_SubDHashType hash_type, - const ON_SubD& parent_subd, bool bForceUpdate ) const { - const unsigned vertex_count = parent_subd.VertexCount(); + const unsigned vertex_count = this->ActiveLevel().m_vertex_count; if (0 == vertex_count) return ON_SubDHash::Empty; - if (ON_SubDHashType::Geometry != hash_type && ON_SubDHashType::Topology != hash_type) + // m_subd_toplologyX_hash, m_subd_toplology_and_edge_crease_hash, and m_subd_geometry_hash + // are mutable and use lazy evaluation to stay updated. + // subd.GeometryContentSerialNumber() is used to detect stale values. + ON_SubDHash* h; + switch (hash_type) + { + case ON_SubDHashType::Topology: + h = &this->m_subd_toplology_hash; + break; + case ON_SubDHashType::TopologyAndEdgeCreases: + h = &this->m_subd_toplology_and_edge_creases_hash; + break; + case ON_SubDHashType::Geometry: + h = &this->m_subd_geometry_hash; + break; + default: + h = nullptr; + break; + } + if ( nullptr == h) return ON_SubDHash::Empty; - // m_subd_geometry_hash and m_subd_toplology_hash are mutable and use lazy evaluation to stay updated. - // subd.GeometryContentSerialNumber() is used to detect stale values. - ON_SubDHash& h - = (ON_SubDHashType::Geometry == hash_type) - ? this->m_subd_geometry_hash - : this->m_subd_toplology_hash - ; - const ON__UINT64 rsn = parent_subd.RuntimeSerialNumber(); - const ON__UINT64 gsn = parent_subd.GeometryContentSerialNumber(); + const unsigned edge_count = this->ActiveLevel().m_edge_count; + const unsigned face_count = this->ActiveLevel().m_face_count; + const ON__UINT64 rsn = this->RuntimeSerialNumber; + const ON__UINT64 gsn = this->GeometryContentSerialNumber(); if ( false == bForceUpdate - && h.IsNotEmpty() - && hash_type == h.HashType() - && rsn > 0 && rsn == h.SubDRuntimeSerialNumber() - && gsn > 0 && gsn == h.SubDGeometryContentSerialNumber() - && vertex_count == h.VertexCount() - && parent_subd.EdgeCount() == h.EdgeCount() - && parent_subd.FaceCount() == h.FaceCount() + && h->IsNotEmpty() + && hash_type == h->HashType() + && rsn > 0 && rsn == h->SubDRuntimeSerialNumber() + && gsn > 0 && gsn == h->SubDGeometryContentSerialNumber() + && vertex_count == h->VertexCount() + && edge_count == h->EdgeCount() + && face_count == h->FaceCount() ) { - // The chached hash values are up to date (or should be). + // The chache hash values are up to date (or should be). // If h is out of date, something somewhere modified the SubD components and // failed to change the GeometryContentSerialNumber(). // All C++ SDK opennurbs code changes gsn after modifying SubD geometry (or it's a bug that should be fixed). // The unwashed masses can do just about anything and that's why the bForceUpdate parameter is supplied. - return h; + return *h; } // update cached value - h = ON_SubDHash::Create(hash_type,parent_subd); + *h = ON_SubDHash::Create(hash_type,this); // return updated value - return h; + return *h; } ON__UINT64 ON_SubD::RenderContentSerialNumber() const @@ -5935,7 +6597,9 @@ bool ON_SubD::IsValid(ON_TextLog* text_logx) const //virtual void ON_SubD::Dump(ON_TextLog& text_log) const { - unsigned int component_sample_count = 16; // dump the first 16 vertices, edges, faces + // At maximum verbosity, dump all vertices, edges, faces, else dump the first 16. + const unsigned component_sample_count = text_log.LevelOfDetailIsAtLeast(ON_TextLog::LevelOfDetail::Maximum) ? 0x7FFFFFFF : 16; + ON_2udex id_range; id_range.i = component_sample_count; id_range.j = 0; @@ -5947,6 +6611,31 @@ unsigned int ON_SubD::DumpTopology(ON_TextLog & text_log) const return DumpTopology(ON_2udex::Zero,ON_2udex::Zero,ON_2udex::Zero,text_log); } +static const ON_wString Internal_DescribeWaste(size_t waste, size_t total) +{ + if (waste <= 0 || total <= 0) + return ON_wString::ToMemorySize(0); + + double p = 100.0 * ((double)waste) / ((double)total); + if (p != p) + return ON_wString::EmptyString; + const double i = (p - floor(p) <= 0.5) ? floor(p) : ceil(p); + double e = fabs(i - p); + + ON_wString description = ON_wString::EmptyString; + const double negligable = 0.1; + if (e < negligable) + { + if (0.0 == i) + description = L" negligable"; + p = i; + } + if (description.IsEmpty()) + description = (i >= 10.0) ? ON_wString::FormatToString(L"%g%% of total", i) : ON_wString::FormatToString(L"%0.1f%% of total)", p); + if (waste > 0) + description += ON_wString(L" (") + ON_wString::ToMemorySize(waste) + ON_wString(L")"); + return description; +} unsigned int ON_SubD::DumpTopology( ON_2udex vertex_id_range, @@ -5972,14 +6661,40 @@ unsigned int ON_SubD::DumpTopology( const ON__UINT64 geometry_content_sn = (bIsTextHash) ? 0 : this->GeometryContentSerialNumber(); const ON__UINT64 render_content_sn = (bIsTextHash) ? 0 : this->RenderContentSerialNumber(); + const unsigned subd_vertex_count = VertexCount(); + const unsigned subd_edge_count = EdgeCount(); + const unsigned subd_face_count = FaceCount(); if (level_count > 1) - text_log.Print(L"SubD[%" PRIu64 "]: %u levels. Level %u is active.\n", - runtime_sn, + text_log.Print(L"SubD[%" PRIu64 "]: %u levels. Level %u is active (%u vertices, %u edges, %u faces).\n", + runtime_sn, level_count, - active_level_index + active_level_index, + subd_vertex_count, + subd_edge_count, + subd_face_count ); else - text_log.Print(L"SubD[%" PRIu64 "]:\n", runtime_sn); + { + text_log.Print( + L"SubD[%" PRIu64 "]: %u vertices, %u edges, %u faces\n", + runtime_sn, + subd_vertex_count, + subd_edge_count, + subd_face_count + ); + } + + const ON_SubDHashType htype[] = { + ON_SubDHashType::Topology, + ON_SubDHashType::TopologyAndEdgeCreases, + ON_SubDHashType::Geometry + }; + + for (size_t i = 0; i < sizeof(htype) / sizeof(htype[0]); ++i) + { + const ON_SubDHash h = this->SubDHash(htype[i], false); + h.Dump(text_log); + } text_log.Print(L"Texture coordinate settings:\n"); { @@ -6044,13 +6759,13 @@ unsigned int ON_SubD::DumpTopology( text_log.Print("box"); break; case ON_TextureMapping::TYPE::mesh_mapping_primitive: - text_log.Print("mesh primative"); + text_log.Print("mesh primitive"); break; case ON_TextureMapping::TYPE::srf_mapping_primitive: - text_log.Print("srf primative"); + text_log.Print("srf primitive"); break; case ON_TextureMapping::TYPE::brep_mapping_primitive: - text_log.Print("brep primative"); + text_log.Print("brep primitive"); break; case ON_TextureMapping::TYPE::ocs_mapping: text_log.Print("ocs"); @@ -6097,9 +6812,22 @@ unsigned int ON_SubD::DumpTopology( } } + bool bIncludeSymmetrySet = false; text_log.Print(L"Geometry content serial number = %" PRIu64 "\n", geometry_content_sn); text_log.Print(L"Render content serial number = %" PRIu64 "\n", render_content_sn); + text_log.Print("Heap use:\n"); + { + ON_TextLogIndent indent1(text_log); + size_t sizeof_subd = this->SizeOfAllElements(); + text_log.PrintString(ON_wString(L"Total = ") + ON_wString::ToMemorySize(sizeof_subd) + ON_wString(L".\n")); + const size_t sizeof_frags = this->SizeOfAllMeshFragments(); + text_log.PrintString(ON_wString(L"Mesh fragments = ") + ON_wString::ToMemorySize(sizeof_frags) + ON_wString(L".\n")); + const size_t sizeof_frags_waste = this->SizeOfUnusedMeshFragments(); + text_log.PrintString(ON_wString(L"Reserved but ununsed = ") + + Internal_DescribeWaste(sizeof_frags_waste,sizeof_subd) + + ON_wString(L".\n")); + } text_log.Print(L"Levels:\n"); @@ -6131,6 +6859,7 @@ unsigned int ON_SubD::DumpTopology( : empty_id_range; error_count += level->DumpTopology( + *this, subdimple->MaximumVertexId(), subdimple->MaximumEdgeId(), subdimple->MaximumFaceId(), @@ -6140,6 +6869,7 @@ unsigned int ON_SubD::DumpTopology( vidit, eidit, fidit, + bIncludeSymmetrySet, text_log); } @@ -6154,6 +6884,7 @@ ON_SubDHashType ON_SubDHashTypeFromUnsigned( { ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDHashType::Unset); ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDHashType::Topology); + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDHashType::TopologyAndEdgeCreases); ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDHashType::Geometry); } return ON_SUBD_RETURN_ERROR(ON_SubDHashType::Unset); @@ -6173,6 +6904,9 @@ const ON_wString ON_SubDHashTypeToString( case ON_SubDHashType::Topology: name = L"Topology"; break; + case ON_SubDHashType::TopologyAndEdgeCreases: + name = L"TopologyAndEdgeCreases"; + break; case ON_SubDHashType::Geometry: name = L"Geometry"; break; @@ -6184,7 +6918,51 @@ const ON_wString ON_SubDHashTypeToString( return bVerbose ? (ON_wString(L"ON_SubDHashType::") + ON_wString(name)) : ON_wString(name); } +ON_SubDEndCapStyle ON_SubDEndCapStyleFromUnsigned( + unsigned int subd_cap_style_as_unsigned +) +{ + switch (subd_cap_style_as_unsigned) + { + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDEndCapStyle::Unset); + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDEndCapStyle::None); + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDEndCapStyle::Triangles); + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDEndCapStyle::Quads); + ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDEndCapStyle::Ngon); + } + return ON_SUBD_RETURN_ERROR(ON_SubDEndCapStyle::Unset); +} +const ON_wString ON_SubDEndCapStyleToString( + ON_SubDEndCapStyle subd_cap_style, + bool bVerbose +) +{ + const wchar_t* s; + switch (subd_cap_style) + { + case ON_SubDEndCapStyle::Unset: + s = L"Unset"; + break; + case ON_SubDEndCapStyle::None: + s = L"None"; + break; + case ON_SubDEndCapStyle::Triangles: + s = L"Triangles"; + break; + case ON_SubDEndCapStyle::Quads: + s = L"Quads"; + break; + case ON_SubDEndCapStyle::Ngon: + s = L"Ngon"; + break; + default: + s = L"invalid"; + break; + } + + return ON_wString(s); +} static void Internal_AccumulateVertexHash( ON_SHA1& sha1, @@ -6234,6 +7012,13 @@ const ON_SHA1_Hash ON_SubD::VertexHash(ON_SubDHashType hash_type) const return Internal_VertexHash(hash_type, FirstVertex(), this->ActiveLevelIndex(), vidit); } +const ON_SHA1_Hash ON_SubDimple::VertexHash(ON_SubDHashType hash_type) const +{ + ON_SubDVertexIdIterator vidit; + this->InitializeVertexIdIterator(vidit); + return Internal_VertexHash(hash_type, ActiveLevel().m_vertex[0], this->ActiveLevelIndex(), vidit); +} + static void Internal_AccumulateEdgeHash( ON_SHA1& sha1, ON_SubDHashType hash_type, @@ -6243,7 +7028,13 @@ static void Internal_AccumulateEdgeHash( sha1.AccumulateInteger32(e->m_id); sha1.AccumulateInteger32(e->VertexId(0)); sha1.AccumulateInteger32(e->VertexId(1)); - sha1.AccumulateBool(e->IsCrease()); + + if (ON_SubDHashType::TopologyAndEdgeCreases == hash_type || ON_SubDHashType::Geometry == hash_type) + { + // Changing edge crease/smooth attributes often changes the regions used in face packing and exploding. + sha1.AccumulateBool(e->IsCrease()); + } + if (ON_SubDHashType::Geometry == hash_type) { if (e->SubdivisionDisplacementIsNonzero()) @@ -6284,6 +7075,14 @@ const ON_SHA1_Hash ON_SubD::EdgeHash(ON_SubDHashType hash_type) const return Internal_EdgeHash(hash_type,FirstEdge(), this->ActiveLevelIndex(), eidit); } + +const ON_SHA1_Hash ON_SubDimple::EdgeHash(ON_SubDHashType hash_type) const +{ + ON_SubDEdgeIdIterator eidit; + this->InitializeEdgeIdIterator(eidit); + return Internal_EdgeHash(hash_type, ActiveLevel().m_edge[0], this->ActiveLevelIndex(), eidit); +} + static void Internal_AccumulateFaceHash( ON_SHA1& sha1, ON_SubDHashType hash_type, @@ -6344,17 +7143,12 @@ const ON_SHA1_Hash ON_SubD::FaceHash(ON_SubDHashType hash_type) const return Internal_FaceHash(hash_type,FirstFace(), this->ActiveLevelIndex(), fidit); } -//const ON_SHA1_Hash ON_SubD::SubDHash(ON_SubDHashType hash_type) const -//{ -// ON_SHA1 sha1; -// if ( VertexCount() > 0 ) -// sha1.AccumulateSubHash(VertexHash(hash_type)); -// if (EdgeCount() > 0) -// sha1.AccumulateSubHash(EdgeHash(hash_type)); -// if (FaceCount() > 0) -// sha1.AccumulateSubHash(FaceHash(hash_type)); -// return sha1.Hash(); -//} +const ON_SHA1_Hash ON_SubDimple::FaceHash(ON_SubDHashType hash_type) const +{ + ON_SubDFaceIdIterator fidit; + this->InitializeFaceIdIterator(fidit); + return Internal_FaceHash(hash_type, ActiveLevel().m_face[0], this->ActiveLevelIndex(), fidit); +} static void Internal_AccumulateFragmentArrayHash(ON_SHA1& sha1, size_t dim, const double* a, unsigned count, size_t stride) { @@ -6372,20 +7166,29 @@ static void Internal_AccumulateFragmentArrayHash(ON_SHA1& sha1, size_t dim, cons const ON_SubDHash ON_SubDHash::Create(ON_SubDHashType hash_type, const class ON_SubD& subd) { + return ON_SubDHash::Create(hash_type, subd.SubDimple()); +} + +const ON_SubDHash ON_SubDHash::Create(ON_SubDHashType hash_type, const class ON_SubDimple* subdimple) +{ + if (nullptr == subdimple) + return ON_SubDHash::Empty; + + const ON_SubDLevel& active_level = subdimple->ActiveLevel(); ON_SubDHash h; h.m_hash_type = hash_type; - h.m_vertex_count = subd.VertexCount(); - h.m_edge_count = subd.EdgeCount(); - h.m_face_count = subd.FaceCount(); - h.m_subd_runtime_serial_number = subd.RuntimeSerialNumber(); + h.m_vertex_count = active_level.m_vertex_count; + h.m_edge_count = active_level.m_edge_count; + h.m_face_count = active_level.m_face_count; + h.m_subd_runtime_serial_number = subdimple->RuntimeSerialNumber; if (h.m_vertex_count > 0) { - h.m_subd_geometry_content_serial_number = subd.GeometryContentSerialNumber(); + h.m_subd_geometry_content_serial_number = subdimple->GeometryContentSerialNumber(); if (ON_SubDHashType::Unset != hash_type) { - h.m_vertex_hash = subd.VertexHash(hash_type); - h.m_edge_hash = subd.EdgeHash(hash_type); - h.m_face_hash = subd.FaceHash(hash_type); + h.m_vertex_hash = subdimple->VertexHash(hash_type); + h.m_edge_hash = subdimple->EdgeHash(hash_type); + h.m_face_hash = subdimple->FaceHash(hash_type); } } return h; @@ -6425,7 +7228,9 @@ void ON_SubDHash::Dump(ON_TextLog& text_log) const { if (text_log.IsTextHash()) return; + bool bIsNotEmpty = IsNotEmpty(); + if (bIsNotEmpty) { switch (this->HashType()) @@ -6433,6 +7238,9 @@ void ON_SubDHash::Dump(ON_TextLog& text_log) const case ON_SubDHashType::Topology: text_log.Print("SubD toplogy hash:\n"); break; + case ON_SubDHashType::TopologyAndEdgeCreases: + text_log.Print("SubD toplogy and edge creases hash:\n"); + break; case ON_SubDHashType::Geometry: text_log.Print("SubD geometry hash:\n"); break; @@ -6441,15 +7249,16 @@ void ON_SubDHash::Dump(ON_TextLog& text_log) const break; } } + if (bIsNotEmpty) - text_log.Print("SubD hash: Empty\n"); - else { - const ON_TextLogIndent indent(text_log); + const ON_TextLogIndent indent1(text_log); const unsigned vcount = this->VertexCount(); const unsigned ecount = this->EdgeCount(); const unsigned fcount = this->FaceCount(); - + const ON_wString subdsha1 = this->SubDHash().ToStringEx(true); + text_log.Print(L"SubD SHA1 = %ls\n", static_cast(subdsha1)); + const ON_TextLogIndent indent2(text_log); if (vcount > 0) { const ON_wString vsha1 = this->VertexHash().ToStringEx(true); @@ -6471,8 +7280,11 @@ void ON_SubDHash::Dump(ON_TextLog& text_log) const const ON_wString fsha1 = this->FaceHash().ToStringEx(true); text_log.Print(L"%u faces. SHA1 = %ls\n", fcount, static_cast(fsha1)); } - text_log.Print("No faces.\n"); + else + text_log.Print("No faces.\n"); } + else + text_log.Print("SubD hash: Empty\n"); } bool ON_SubDHash::Write(class ON_BinaryArchive& archive) const @@ -6763,7 +7575,9 @@ static const ON_SHA1_Hash Internal_FragmentCurvaturesHash(const ON_SubDFace* fir return bNotEmpty ? sha1.Hash() : ON_SHA1_Hash::EmptyContentHash; } + unsigned int ON_SubDLevel::DumpTopology( + const ON_SubD& parent_subd, const unsigned int validate_max_vertex_id, const unsigned int validate_max_edge_id, const unsigned int validate_max_face_id, @@ -6773,6 +7587,7 @@ unsigned int ON_SubDLevel::DumpTopology( ON_SubDVertexIdIterator& vidit, ON_SubDEdgeIdIterator& eidit, ON_SubDFaceIdIterator& fidit, + bool bIncludeSymmetrySet, ON_TextLog& text_log ) const { @@ -7107,30 +7922,22 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print(L"...\n"); bSkippedPreviousComponent = false; } - if (v->m_group_id > 0) - { - text_log.Print("v%u: group_id=%u ", v->m_id, v->m_group_id); - } - else - { - text_log.Print("v%u: ", v->m_id); - } - if (bIsDamaged) { - text_log.Print( - "(DAMAGED) %ls (%g, %g, %g)\n", - static_cast(vtag), - P0.x, P0.y, P0.z - ); - } - else - { - text_log.Print( - "%ls (%g, %g, %g)\n", - static_cast(vtag), - P0.x, P0.y, P0.z - ); + ON_String s = ON_String::FormatToString("v%u: ",v->m_id); + + if (bIsDamaged) + s += "(DAMAGED) "; + + s += ON_String::FormatToString( + "%ls (%g, %g, %g)", + static_cast(vtag), + P0.x, P0.y, P0.z + ); + if (v->m_group_id > 0) + s += ON_String::FormatToString(" group_id=%u", v->m_group_id); + text_log.PrintString(s); + text_log.PrintNewLine(); } text_log.PushIndent(); @@ -7148,58 +7955,58 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print( "v.SurfacePoint: (%g, %g, %g)\n", S.x, S.y, S.z ); const unsigned int vertex_edge_count = v->m_edge_count; - text_log.Print("v.Edges[%u] = {", vertex_edge_count); - prefix[0] = ON_String::Space; - prefix[1] = error_code_point; - prefix[2] = 'e'; - prefix[3] = 0; + text_log.Print("v.Edges[%u] = ", vertex_edge_count); + prefix[0] = '{'; + prefix[1] = ON_String::Space; + prefix[2] = error_code_point; + prefix[3] = 'e'; + prefix[4] = '%'; + prefix[5] = 'u'; + prefix[6] = 0; for (unsigned int vei = 0; vei < vertex_edge_count; vei++) { - prefix[1] = error_code_point; if (1 == vei) - { prefix[0] = ','; - } + prefix[2] = error_code_point; const ON_SubDEdge* e = v->Edge(vei); - unsigned int eid = 0; if (nullptr != e) { if (v == e->m_vertex[0] && v != e->m_vertex[1]) - prefix[1] = '+'; + prefix[2] = '+'; else if (v != e->m_vertex[0] && v == e->m_vertex[1]) - prefix[1] = '-'; - eid = e->m_id; + prefix[2] = '-'; + else + vertex_error_count++; + text_log.Print(prefix, e->m_id); } - text_log.Print("%s%u", prefix, eid); - if (error_code_point == prefix[1]) + else + { + text_log.Print("%c %c", prefix[0], error_code_point); vertex_error_count++; + } } text_log.Print(" }\n"); const unsigned int vertex_face_count = v->m_face_count; - text_log.Print("v.Faces[%u] = {", vertex_face_count); - prefix[0] = ON_String::Space; + text_log.Print("v.Faces[%u] = ", vertex_face_count); + prefix[0] = '{'; prefix[1] = ON_String::Space; prefix[2] = 'f'; - prefix[3] = 0; + prefix[3] = '%'; + prefix[4] = 'u'; + prefix[5] = 0; for (unsigned int vfi = 0; vfi < vertex_face_count; vfi++) { - prefix[1] = error_code_point; if (1 == vfi) - { prefix[0] = ','; - } const ON_SubDFace* f = v->Face(vfi); - unsigned int fid = 0; if (nullptr != f) + text_log.Print(prefix, f->m_id); + else { - if (f->VertexIndex(v) < ON_UNSET_UINT_INDEX) - prefix[1] = ON_String::Space; - fid = f->m_id; - } - text_log.Print("%s%u", prefix, fid); - if (error_code_point == prefix[1]) + text_log.Print("%c %c", prefix[0], error_code_point); vertex_error_count++; + } } text_log.Print(" }\n"); text_log.PopIndent(); @@ -7307,85 +8114,60 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print(L"...\n"); bSkippedPreviousComponent = false; } - if (e->m_group_id > 0) - { - text_log.Print("e%u: group_id=%u ", e->m_id, e->m_group_id); - } - else - { - text_log.Print("e%u: ", e->m_id); - } - if (bIsDamaged) - { - if (bIsWireEdge) - { - text_log.Print( - "(DAMAGED) %ls wire (", - static_cast(etag) - ); - } - else if (bIsNonmanifoldEdge) - { - text_log.Print( - "(DAMAGED) %ls nonmanifold (", - static_cast(etag) - ); - } - else - { - text_log.Print( - "(DAMAGED) %ls (", - static_cast(etag) - ); - } - } - else - { - if (bIsWireEdge) - { - text_log.Print( - "wire %ls (", - static_cast(etag) - ); - } - else if (bIsNonmanifoldEdge) - { - text_log.Print( - "nonmanifold %ls (", - static_cast(etag) - ); - } - else - { - text_log.Print( - "%ls (", - static_cast(etag) - ); - } - } - prefix[0] = ON_String::Space; - prefix[1] = error_code_point; - prefix[2] = 'v'; - prefix[3] = 0; - for (unsigned int evi = 0; evi < 2; evi++) { - if (1 == evi) - text_log.Print(" to"); - prefix[1] = error_code_point; - const ON_SubDVertex* v = e->m_vertex[evi]; - unsigned int vid = 0; - if (nullptr != v) + ON_String s = ON_String::FormatToString("e%u: ", e->m_id); + if (bIsDamaged) + s += "(DAMAGED) "; + + if (bIsWireEdge) { - vid = v->m_id; - if (v->EdgeArrayIndex(e) < ON_UNSET_INT_INDEX) - prefix[1] = ON_String::Space; + s += ON_String::FormatToString( + "wire %ls ", + static_cast(etag) + ); } - if (error_code_point == prefix[1]) - edge_error_count++; - text_log.Print("%s%u", (0==evi)?(prefix+1):(prefix), vid); + else if (bIsNonmanifoldEdge) + { + s += ON_String::FormatToString( + "nonmanifold %ls ", + static_cast(etag) + ); + } + else + { + s += ON_String::FormatToString( + "%ls ", + static_cast(etag) + ); + } + + s += "( "; + prefix[0] = 'v'; + prefix[1] = '%'; + prefix[2] = 'u'; + prefix[3] = 0; + for (unsigned int evi = 0; evi < 2; evi++) + { + if (1 == evi) + s += " to "; + const ON_SubDVertex* v = e->m_vertex[evi]; + if (nullptr != v) + s += ON_String::FormatToString(prefix, v->m_id); + else + { + s += error_code_point; + edge_error_count++; + } + } + s += " )"; + + if (e->m_group_id > 0) + s += ON_String::FormatToString(" group_id=%u", e->m_group_id); + + text_log.PrintString(s); + text_log.PrintNewLine(); } - text_log.Print(")\n"); text_log.PushIndent(); @@ -7398,36 +8180,40 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print( "e.SubdivisionPoint: (%g, %g, %g)\n", P1.x, P1.y, P1.z ); const unsigned int edge_face_count = e->m_face_count; - text_log.Print("e.Faces[%u] = {", edge_face_count); - prefix[0] = ON_String::Space; - prefix[1] = error_code_point; - prefix[2] = 'f'; - prefix[3] = 0; + text_log.Print("e.Faces[%u] = ", edge_face_count); + prefix[0] = '{'; + prefix[1] = ON_String::Space; + prefix[2] = error_code_point; + prefix[3] = 'f'; + prefix[4] = '%'; + prefix[5] = 'u'; + prefix[6] = 0; for (unsigned int efi = 0; efi < edge_face_count; efi++) { - prefix[1] = error_code_point; if (1 == efi) - { prefix[0] = ','; - } + prefix[2] = error_code_point; ON_SubDFacePtr fptr = e->FacePtr(efi); const ON_SubDFace* f = fptr.Face(); const ON__UINT_PTR edge_fdir = fptr.FaceDirection(); - unsigned int fid = 0; if (nullptr != f) { - fid = f->m_id; ON_SubDEdgePtr eptr = f->EdgePtrFromEdge(e); if (eptr.Edge() == e && eptr.EdgeDirection() == edge_fdir) - { - prefix[1] = (0 == edge_fdir) ? '+' : '-'; - } + prefix[2] = ((0 == edge_fdir) ? '+' : '-'); + else + edge_error_count++; + text_log.Print(prefix, f->m_id); } - if (error_code_point == prefix[1]) + else + { + text_log.Print("%c %c", prefix[0], error_code_point); edge_error_count++; - text_log.Print("%s%u", prefix, fid); + } } text_log.Print(" }\n"); + + text_log.PopIndent(); } @@ -7526,20 +8312,14 @@ unsigned int ON_SubDLevel::DumpTopology( f->m_id ); } - else if (f->m_group_id > 0) - { - text_log.Print( - "f%u: group_id=%u\n", - f->m_id, - f->m_group_id - ); - } else { - text_log.Print( - "f%u:\n", - f->m_id - ); + ON_String s = ON_String::FormatToString("f%u:", f->m_id); + + if (f->m_group_id > 0) + s += ON_String::FormatToString(" group_id=%u", f->m_group_id); + text_log.PrintString(s); + text_log.PrintNewLine(); } text_log.PushIndent(); @@ -7554,81 +8334,62 @@ unsigned int ON_SubDLevel::DumpTopology( const unsigned int face_edge_count = f->m_edge_count; - text_log.Print("f.Edges[%u] = {", face_edge_count); - prefix[0] = ON_String::Space; - prefix[1] = error_code_point; - prefix[2] = 'e'; - prefix[3] = 0; + text_log.Print("f.Edges[%u] = ", face_edge_count); + prefix[0] = '{'; + prefix[1] = ON_String::Space; + prefix[2] = error_code_point; + prefix[3] = 'e'; + prefix[4] = '%'; + prefix[5] = 'u'; + prefix[6] = 0; for (unsigned int fei = 0; fei < face_edge_count; fei++) { - prefix[1] = error_code_point; if (1 == fei) - { prefix[0] = ','; - } + prefix[2] = error_code_point; const ON_SubDEdgePtr eptr = f->EdgePtr(fei); const ON_SubDEdge* e = eptr.Edge(); const ON__UINT_PTR face_edir = eptr.EdgeDirection(); - unsigned int eid = 0; if (nullptr != e) { - eid = e->m_id; ON_SubDFacePtr fptr = e->FacePtrFromFace(f); if (fptr.Face() == f && fptr.FaceDirection() == face_edir) - { - prefix[1] = (0 == face_edir) ? '+' : '-'; - } - } - if (error_code_point == prefix[1]) - face_error_count++; - text_log.Print("%s%u", prefix, eid); - } - text_log.Print(" }\n"); - - const ON_SubDEdgePtr last_eptr = f->EdgePtr(face_edge_count - 1); - const ON_SubDEdge* last_edge = last_eptr.Edge(); - const ON_SubDVertex* v1 - = (nullptr != last_edge) - ? last_edge->m_vertex[0 == last_eptr.EdgeDirection() ? 1 : 0] - : nullptr; - - text_log.Print("f.Vertices[%u] = {", face_edge_count); - prefix[0] = ON_String::Space; - prefix[1] = error_code_point; - prefix[2] = 'v'; - prefix[3] = 0; - for (unsigned int fei = 0; fei < face_edge_count; fei++) - { - prefix[1] = error_code_point; - if (1 == fei) - { - prefix[0] = ','; - } - const ON_SubDEdgePtr eptr = f->EdgePtr(fei); - const ON_SubDEdge* e = eptr.Edge(); - const ON__UINT_PTR face_edir = eptr.EdgeDirection(); - unsigned int vid = 0; - if (nullptr == e) - { - v1 = nullptr; + prefix[2] = ((0 == face_edir) ? '+' : '-'); + else + face_error_count++; + text_log.Print(prefix, e->m_id); } else { - const ON_SubDVertex* v0 = e->m_vertex[0 == face_edir ? 0 : 1]; - if (nullptr != v0) - { - vid = v0->m_id; - if (v1 == v0) - prefix[1] = ON_String::Space; - } - v1 = e->m_vertex[0 == face_edir ? 1 : 0]; - } - if (error_code_point == prefix[1]) + text_log.Print("%c %c", prefix[0], error_code_point); face_error_count++; - text_log.Print("%s%u", prefix, vid); + } } text_log.Print(" }\n"); + text_log.Print("f.Vertices[%u] = ", face_edge_count); + prefix[0] = '{'; + prefix[1] = ON_String::Space; + prefix[2] = 'v'; + prefix[3] = '%'; + prefix[4] = 'u'; + prefix[5] = 0; + for (unsigned int fvi = 0; fvi < face_edge_count; fvi++) + { + if (1 == fvi) + prefix[0] = ','; + const ON_SubDVertex* v = f->Vertex(fvi); + if (nullptr != v) + text_log.Print(prefix, v->m_id); + else + { + text_log.Print("%c %c", prefix[0], error_code_point); + face_error_count++; + } + } + text_log.Print(" }\n"); + + if (f->TexturePointsAreSet()) { text_log.Print("f.TexturePoints[%u] = {", face_edge_count); @@ -7688,7 +8449,7 @@ unsigned int ON_SubDLevel::DumpTopology( f->PackRectCorner(bGridOrder,2), f->PackRectCorner(bGridOrder,3) }; - text_log.Print(L"PackId=%u Pack rectangle corners: (%g,%g), (%g,%g), (%g,%g), (%g,%g)", + text_log.Print("f.PackId = %u Pack rectangle corners: (%g,%g), (%g,%g), (%g,%g), (%g,%g)", f->PackId(), corners[0].x, corners[0].y, corners[1].x, corners[1].y, @@ -7883,6 +8644,42 @@ unsigned int ON_SubD::SizeOf() const return (unsigned int)sz; } +size_t ON_SubD::SizeOfAllElements() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfAllElements() : 0; +} + +size_t ON_SubD::SizeOfActiveElements() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfActiveElements() : 0; +} + +size_t ON_SubD::SizeOfUnusedElements() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfUnusedElements() : 0; +} + +size_t ON_SubD::SizeOfAllMeshFragments() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfAllMeshFragments() : 0; +} + +size_t ON_SubD::SizeOfActiveMeshFragments() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfActiveMeshFragments() : 0; +} + +size_t ON_SubD::SizeOfUnusedMeshFragments() const +{ + const ON_SubDimple* subdimple = SubDimple(); + return (nullptr != subdimple) ? subdimple->SizeOfUnusedMeshFragments() : 0; +} + //virtual ON__UINT32 ON_SubD::DataCRC(ON__UINT32 current_remainder) const { @@ -8040,6 +8837,78 @@ bool ON_SubD::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) cons // // +class ON_SubDHeap* ON_SubD::Internal_Heap() const +{ + ON_SubDimple* subdimple = m_subdimple_sp.get(); + return (nullptr != subdimple) ? &subdimple->Heap() : nullptr; +} + +bool ON_SubD::InSubD(const class ON_SubDVertex* vertex) const +{ + return InSubD(ON_SubDComponentPtr::Create(vertex)); +} + +bool ON_SubD::InSubD(const class ON_SubDEdge* edge) const +{ + return InSubD(ON_SubDComponentPtr::Create(edge)); +} + +bool ON_SubD::InSubD(const class ON_SubDFace* face) const +{ + return InSubD(ON_SubDComponentPtr::Create(face)); +} + +bool ON_SubD::InSubD(ON_SubDComponentPtr cptr) const +{ + const ON_SubDHeap* h = this->Internal_Heap(); + return (nullptr != h) ? h->InHeap(cptr) : false; +} + +const ON_SubDComponentPtr ON_SubD::InSubD(const ON_SubDComponentBase* b) const +{ + const ON_SubDHeap* h = this->Internal_Heap(); + return (nullptr != h) ? h->InHeap(b) : ON_SubDComponentPtr::Null; +} + + +bool ON_SubDHeap::InHeap(ON_SubDComponentPtr cptr) const +{ + const ON_FixedSizePool* fsp = this->Internal_ComponentFixedSizePool(cptr.ComponentType()); + return (nullptr != fsp) ? fsp->InPool(cptr.ComponentBase()) : false; +} + +const ON_SubDComponentPtr ON_SubDHeap::InHeap(const class ON_SubDComponentBase* b) const +{ + if (nullptr != b) + { + ON_SubDComponentPtr::Type t[3] = { + ON_SubDComponentPtr::Type::Vertex, + ON_SubDComponentPtr::Type::Edge, + ON_SubDComponentPtr::Type::Face + }; + for (int i = 0; i < 3; ++i) + { + const ON_FixedSizePool* fsp = this->Internal_ComponentFixedSizePool(t[i]); + if (nullptr != fsp && fsp->InPool(b)) + { + switch (t[i]) + { + case ON_SubDComponentPtr::Type::Vertex: + return ON_SubDComponentPtr::Create((const ON_SubDVertex*)b); + break; + case ON_SubDComponentPtr::Type::Edge: + return ON_SubDComponentPtr::Create((const ON_SubDEdge*)b); + break; + case ON_SubDComponentPtr::Type::Face: + return ON_SubDComponentPtr::Create((const ON_SubDFace*)b); + break; + } + } + } + } + return ON_SubDComponentPtr::Null; +} + const class ON_SubDLevel& ON_SubD::ActiveLevel() const { ON_SubDimple* subdimple = m_subdimple_sp.get(); @@ -8208,7 +9077,26 @@ ON_SubDVertex* ON_SubD::AddVertexForExperts( return v; } +bool ON_SubD::ReturnVertexForExperts( + ON_SubDVertex* v +) +{ + if (nullptr == v) + return false; + if (this->InSubD(v) && v->IsActive() && 0 == v->m_edge_count && 0 == v->m_face_count ) + { + ON_SubDimple* subdimple = SubDimple(false); + if (nullptr != subdimple) + { + subdimple->ReturnVertex(v); + return true; + } + } + + // Caller is not an expert but a crash has been prevented. + return ON_SUBD_RETURN_ERROR(false); +} class ON_SubDEdge* ON_SubDimple::AddEdge( ON_SubDEdgeTag edge_tag, @@ -8483,10 +9371,30 @@ class ON_SubDEdge* ON_SubD::AddEdgeForExperts( { ON_SubDimple* subdimple = SubDimple(true); if (nullptr != subdimple) - return subdimple->AddEdge( candidate_edge_id, edge_tag, v0, v0_sector_coefficient, v1, v1_sector_coefficient, initial_face_capacity); + return subdimple->AddEdge(candidate_edge_id, edge_tag, v0, v0_sector_coefficient, v1, v1_sector_coefficient, initial_face_capacity); return ON_SUBD_RETURN_ERROR(nullptr); } +bool ON_SubD::ReturnEdgeForExperts( + ON_SubDEdge* e +) +{ + if (nullptr == e) + return false; + + if (this->InSubD(e) && e->IsActive() && 0 == e->m_face_count && nullptr == e->m_vertex[0] && nullptr == e->m_vertex[1]) + { + ON_SubDimple* subdimple = SubDimple(false); + if (nullptr != subdimple) + { + subdimple->ReturnEdge(e); + return true; + } + } + + // Caller is not an expert but a crash has been prevented. + return ON_SUBD_RETURN_ERROR(false); +} class ON_SubDFace* ON_SubDimple::AddFace( @@ -8494,7 +9402,7 @@ class ON_SubDFace* ON_SubDimple::AddFace( const ON_SubDEdgePtr* edge ) { - return AddFace( 0U, edge_count, edge); + return AddFace(0U, edge_count, edge); } class ON_SubDFace* ON_SubDimple::AddFace( @@ -8821,9 +9729,29 @@ class ON_SubDFace* ON_SubD::AddFaceForExperts( ) { ON_SubDimple* subdimple = SubDimple(true); - return (nullptr != subdimple) ? subdimple->AddFace( candiate_face_id, edge_count, edge) : nullptr; + return (nullptr != subdimple) ? subdimple->AddFace(candiate_face_id, edge_count, edge) : nullptr; } +bool ON_SubD::ReturnFaceForExperts( + ON_SubDFace* f +) +{ + if (nullptr == f) + return false; + + if (this->InSubD(f) && f->IsActive() && 0 == f->m_edge_count) + { + ON_SubDimple* subdimple = SubDimple(false); + if (nullptr != subdimple) + { + subdimple->ReturnFace(f); + return true; + } + } + + // Caller is not an expert but a crash has been prevented. + return ON_SUBD_RETURN_ERROR(false); +} bool ON_SubD::AddFaceTexturePoints( const class ON_SubDFace* face, @@ -8942,7 +9870,12 @@ bool ON_SubD::AddFaceEdgeConnection( ON_SubDFace* face, unsigned int i, ON_SubDEdgePtr eptr - ) +) +{ + return AddFaceEdgeConnection(face, i, eptr, false, false); +} + +bool ON_SubD::AddFaceEdgeConnection(ON_SubDFace* face, unsigned int i, ON_SubDEdgePtr eptr, bool bAddbAddFaceToRelativeVertex0, bool bAddbAddFaceToRelativeVertex1) { if (nullptr == face && i >= ON_SubDFace::MaximumEdgeCount) { @@ -9009,6 +9942,65 @@ bool ON_SubD::AddFaceEdgeConnection( face->m_edgex[i-4] = eptr; face->m_edge_count = (unsigned short)face_edge_count; + for (unsigned evi = 0; evi < 2; ++evi) + { + ON_SubDVertex* v = const_cast((0 == evi ? bAddbAddFaceToRelativeVertex0 : bAddbAddFaceToRelativeVertex1) ? eptr.RelativeVertex(evi) : nullptr); + if (nullptr != v) + { + if ( false == this->GrowVertexFaceArray(v, v->m_face_count + 1)) + return ON_SUBD_RETURN_ERROR(false); + v->m_faces[v->m_face_count++] = face; + } + } + + return true; +} + + +bool ON_SubD::SetFaceBoundary( + ON_SubDFace* face, + const ON_SimpleArray& edges +) +{ + return SetFaceBoundary(face, edges.Array(), edges.UnsignedCount()); +} + +bool ON_SubD::SetFaceBoundary( + ON_SubDFace* face, + const ON_SubDEdgePtr* edges, + size_t edge_count +) +{ + // Do a little validation to prevent disasters. + if (nullptr == face) + return ON_SUBD_RETURN_ERROR(false); + if (0 != face->m_edge_count) + return ON_SUBD_RETURN_ERROR(false); + if (nullptr == edges || edge_count < 3 || edge_count > ((size_t)ON_SubDFace::MaximumEdgeCount)) + return ON_SUBD_RETURN_ERROR(false); + const ON_SubDVertex* v1 = edges[edge_count - 1].RelativeVertex(1); + if ( nullptr == v1) + return ON_SUBD_RETURN_ERROR(false); + for (size_t fei = 0; fei < edge_count; ++fei) + { + const ON_SubDVertex* v0 = edges[fei].RelativeVertex(0); + if ( v0 != v1) + return ON_SUBD_RETURN_ERROR(false); + v1 = edges[fei].RelativeVertex(1); + if ( nullptr == v1 || v0 == v1) + return ON_SUBD_RETURN_ERROR(false); + } + + // set face-edge pointers and add face to vertex face arrays + if (false == this->GrowFaceEdgeArray(face, edge_count)) + return ON_SUBD_RETURN_ERROR(false); + for (size_t fei = 0; fei < edge_count; ++fei) + { + ON_SubDEdgePtr eptr = edges[fei]; + if (false == this->AddFaceEdgeConnection(face, (unsigned)fei, eptr, true, false)) + return ON_SUBD_RETURN_ERROR(false); + } + return true; } @@ -9385,6 +10377,28 @@ bool ON_SubDComponentBase::IsActive() const } +bool ON_SubDComponentBase::IsSymmetrySetPrimaryMotif() const +{ + return 1 == this->m_symmetry_set_next.ComponentDirection(); +} + +bool ON_SubDComponentBase::InSymmetrySet() const +{ + return this->m_symmetry_set_next.IsNotNull(); +} + +bool ON_SubDComponentPtr::IsSymmetrySetPrimaryMotif() const +{ + const ON_SubDComponentBase* c = this->ComponentBase(); + return (nullptr != c) ? c->IsSymmetrySetPrimaryMotif() : false; +} + +bool ON_SubDComponentPtr::InSymmetrySet() const +{ + const ON_SubDComponentBase* c = this->ComponentBase(); + return (nullptr != c) ? c->InSymmetrySet() : false; +} + bool ON_SubDComponentBase::Mark() const { return m_status.RuntimeMark(); @@ -10705,14 +11719,15 @@ void ON_SubDVertex::CopyFrom( const ON_SubDVertex* src, bool bCopyEdgeArray, bool bCopyFaceArray, - bool bCopyLimitPointList + bool bCopyLimitPointList, + bool bCopySymmetrySetNext ) { if (nullptr == src) src = &ON_SubDVertex::Empty; ClearSavedSubdivisionPoints(); - CopyBaseFrom(src); + CopyBaseFrom(src, bCopySymmetrySetNext); m_vertex_tag = src->m_vertex_tag; @@ -11331,7 +12346,7 @@ bool ON_SubDimple::LocalSubdivide( ON_SimpleArray face_points(face_count); // this subd is being modifed. - ChangeGeometryContentSerialNumber(false); + ChangeGeometryContentSerialNumber( false); for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face) { @@ -12495,10 +13510,12 @@ bool ON_SubD::IsOriented() const // reverses the orientation of all facets bool ON_SubD::ReverseOrientation() const { + // Limit point normals and limit surface mesh fragments will need to be recalculated. // DestroyRuntimeCache() will clear all this information. const_cast(this)->DestroyRuntimeCache(true); + for (const ON_SubDFace* face = FirstFace(); nullptr != face; face = face->m_next_face) { const_cast(face)->ReverseEdgeList(); @@ -13565,10 +14582,10 @@ bool ON_SubDimple::CopyEvaluationCacheForExperts(const ON_SubDimple& src) { const ON_SubDLevel* src_level = src.ActiveLevelConstPointer(); ON_SubDLevel* this_level = this->ActiveLevelPointer(); - return (nullptr != src_level && nullptr != this_level) ? this_level->CopyEvaluationCacheForExperts(*src_level, this->m_heap) : false; + return (nullptr != src_level && nullptr != this_level) ? this_level->CopyEvaluationCacheForExperts(this->m_heap , *src_level, src.m_heap) : false; } -bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_SubDHeap& this_heap) +bool ON_SubDLevel::CopyEvaluationCacheForExperts( ON_SubDHeap& this_heap, const ON_SubDLevel& src, const ON_SubDHeap& src_heap) { // Validate conditions for coping the cached evaluation information if ( @@ -13579,9 +14596,22 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub ) return ON_SUBD_RETURN_ERROR(false); + src.m_level_index; + // The built in fragment cache always has adaptive ON_SubDDisplayParameters::DefaultDensity const unsigned subd_display_density = ON_SubDDisplayParameters::AbsoluteDisplayDensityFromSubDFaceCount(ON_SubDDisplayParameters::DefaultDensity,m_face_count); + const unsigned this_level_index = this->m_level_index; + const unsigned src_level_index = src.m_level_index; + + // It is critical to use the this_vit/src_vit iterators so we got through the vertices in id order. + // When a copy of an editied subd is made, it is frequently the case that the vertex linked lists + // are in different order. + ON_SubDVertexIdIterator this_vit; + ON_SubDVertexIdIterator src_vit; + this_heap.InitializeVertexIdIterator(this_vit); + src_heap.InitializeVertexIdIterator(src_vit); + ON_SubDVertex* this_vertex; const ON_SubDVertex* src_vertex; ON_SubDEdgePtr this_veptr, src_veptr; @@ -13589,9 +14619,9 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub const ON_SubDFace* src_face; bool bCopyVertexCache = false; for ( - this_vertex = m_vertex[0], src_vertex = src.m_vertex[0]; + this_vertex = const_cast(this_vit.FirstVertexOnLevel(this_level_index)), src_vertex = src_vit.FirstVertexOnLevel(src_level_index); nullptr != this_vertex && nullptr != src_vertex; - this_vertex = const_cast(this_vertex->m_next_vertex), src_vertex = src_vertex->m_next_vertex + this_vertex = const_cast(this_vit.NextVertexOnLevel(this_level_index)), src_vertex = src_vit.NextVertexOnLevel(src_level_index) ) { if (this_vertex->m_id != src_vertex->m_id) @@ -13631,15 +14661,23 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub if (nullptr != this_vertex || nullptr != src_vertex) return ON_SUBD_RETURN_ERROR(false); + // It is critical to use the this_eit/src_eit iterators so we got through the edges in id order. + // When a copy of an editied subd is made, it is frequently the case that the edge linked lists + // are in different order. + ON_SubDEdgeIdIterator this_eit; + ON_SubDEdgeIdIterator src_eit; + this_heap.InitializeEdgeIdIterator(this_eit); + src_heap.InitializeEdgeIdIterator(src_eit); + ON_SubDEdge* this_edge; const ON_SubDEdge* src_edge; const ON_SubDFacePtr* this_fptr; const ON_SubDFacePtr* src_fptr; bool bCopyEdgeCache = false; for ( - this_edge = m_edge[0], src_edge = src.m_edge[0]; + this_edge = const_cast(this_eit.FirstEdgeOnLevel(this_level_index)), src_edge = src_eit.FirstEdgeOnLevel(src_level_index); nullptr != this_edge && nullptr != src_edge; - this_edge = const_cast(this_edge->m_next_edge), src_edge = src_edge->m_next_edge + this_edge = const_cast(this_eit.NextEdgeOnLevel(this_level_index)), src_edge = src_eit.NextEdgeOnLevel(src_level_index) ) { if (this_edge->m_id != src_edge->m_id) @@ -13686,13 +14724,21 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub if (nullptr != this_edge || nullptr != src_edge) return ON_SUBD_RETURN_ERROR(false); + // It is critical to use the this_fit/src_fit iterators so we got through the faces in id order. + // When a copy of an editied subd is made, it is frequently the case that the face linked lists + // are in different order. + ON_SubDFaceIdIterator this_fit; + ON_SubDFaceIdIterator src_fit; + this_heap.InitializeFaceIdIterator(this_fit); + src_heap.InitializeFaceIdIterator(src_fit); + const ON_SubDEdgePtr* this_eptr; const ON_SubDEdgePtr* src_eptr; bool bCopyFaceCache = false; for ( - this_face = m_face[0], src_face = src.m_face[0]; + this_face = const_cast(this_fit.FirstFaceOnLevel(this_level_index)), src_face = src_fit.FirstFaceOnLevel(src_level_index); nullptr != this_face && nullptr != src_face; - this_face = const_cast(this_face->m_next_face), src_face = src_face->m_next_face + this_face = const_cast(this_fit.NextFaceOnLevel(this_level_index)), src_face = src_fit.NextFaceOnLevel(src_level_index) ) { if (this_face->m_id != src_face->m_id) @@ -13734,11 +14780,14 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub double subdivision_point[3]; if (bCopyVertexCache) { + // It is critical to use the this_vit/src_vit iterators so we got through the vertices in id order. + // When a copy of an editied subd is made, it is frequently the case that the vertex linked lists + // are in different order. ON_SubDSectorSurfacePoint this_limit_point; for ( - this_vertex = m_vertex[0], src_vertex = src.m_vertex[0]; + this_vertex = const_cast(this_vit.FirstVertexOnLevel(this_level_index)), src_vertex = src_vit.FirstVertexOnLevel(src_level_index); nullptr != this_vertex && nullptr != src_vertex; - this_vertex = const_cast(this_vertex->m_next_vertex), src_vertex = src_vertex->m_next_vertex + this_vertex = const_cast(this_vit.NextVertexOnLevel(this_level_index)), src_vertex = src_vit.NextVertexOnLevel(src_level_index) ) { if (false == src_vertex->GetSavedSubdivisionPoint(subdivision_point)) @@ -13768,11 +14817,14 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub if (bCopyEdgeCache) { + // It is critical to use the this_eit/src_eit iterators so we got through the edges in id order. + // When a copy of an editied subd is made, it is frequently the case that the edge linked lists + // are in different order. ON_SimpleArray edge_curve_cvs(ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity); for ( - this_edge = m_edge[0], src_edge = src.m_edge[0]; + this_edge = const_cast(this_eit.FirstEdgeOnLevel(this_level_index)), src_edge = src_eit.FirstEdgeOnLevel(src_level_index); nullptr != this_edge && nullptr != src_edge; - this_edge = const_cast(this_edge->m_next_edge), src_edge = src_edge->m_next_edge + this_edge = const_cast(this_eit.NextEdgeOnLevel(this_level_index)), src_edge = src_eit.NextEdgeOnLevel(src_level_index) ) { if (false == src_edge->GetSavedSubdivisionPoint(subdivision_point)) @@ -13786,10 +14838,13 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts(const ON_SubDLevel& src, ON_Sub if (bCopyFaceCache) { + // It is critical to use the this_fit/src_fit iterators so we got through the faces in id order. + // When a copy of an editied subd is made, it is frequently the case that the face linked lists + // are in different order. for ( - this_face = m_face[0], src_face = src.m_face[0]; + this_face = const_cast(this_fit.FirstFaceOnLevel(this_level_index)), src_face = src_fit.FirstFaceOnLevel(src_level_index); nullptr != this_face && nullptr != src_face; - this_face = const_cast(this_face->m_next_face), src_face = src_face->m_next_face + this_face = const_cast(this_fit.NextFaceOnLevel(this_level_index)), src_face = src_fit.NextFaceOnLevel(src_level_index) ) { if (false == src_face->GetSavedSubdivisionPoint(subdivision_point)) @@ -14121,6 +15176,12 @@ unsigned int ON_SubDLevel::UpdateEdgeTags( { next_edge = const_cast(edge->m_next_edge); + if (2 != edge->m_face_count && edge->IsSmooth()) + { + // Dale Lear - Added April 5, 2021 - don't tolerate obvious errors / oversights. + edge->m_edge_tag = ON_SubDEdgeTag::Unset; + } + const ON_SubDEdgeTag edge_tag0 = edge->m_edge_tag; if (bUnsetEdgeTagsOnly && ON_SubDEdgeTag::Unset != edge_tag0 ) { @@ -14927,7 +15988,7 @@ unsigned int ON_SubD::SetComponentMarks( if (nullptr == a) return 0; - for (const ON_SubDComponentBase*const* a1 = a; a < a1; a++) + for (const ON_SubDComponentBase*const* a1 = a + count; a < a1; a++) { const ON_SubDComponentBase* c = *a; if (nullptr == c) @@ -15279,6 +16340,8 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( //unsigned int potential_isolated_vertex_count = 0; unsigned int potential_isolated_edge_count = 0; + ON_SimpleArray moved_vertices; + if (bExtrusionMarking && 0 == cptr_count && nullptr == cptr_list) { // entire subd is being extruded @@ -15303,14 +16366,6 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( } } } - else if ( - bTransform && nullptr != xform - && (ON_SubDComponentLocation::Surface == component_location || ON_SubDComponentLocation::Unset == component_location) - && 1 == cptr_count - && nullptr != cptr_list[0].Vertex() - ) - { - } else { for (size_t i = 0; i < cptr_count; i++) @@ -15329,7 +16384,10 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( { v->m_status.SetRuntimeMark(); if (bTransform) + { const_cast(v)->Transform(false, *xform); + moved_vertices.Append(v->m_id); + } ++marked_vertex_count; } } @@ -15353,6 +16411,7 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( { v->SetMark(); const_cast(v)->Transform(false, *xform); + moved_vertices.Append(v->m_id); ++marked_vertex_count; } } @@ -15392,7 +16451,10 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( { v->m_status.SetRuntimeMark(); if (bTransform) + { const_cast(v)->Transform(false, *xform); + moved_vertices.Append(v->m_id); + } ++marked_vertex_count; } } @@ -15437,9 +16499,16 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( } } + const bool bSymmetryIsSet = + false + ; + + bool bChangePreservesSymmetry = false; + + if (bTransform) { - if (3 * marked_vertex_count >= subd.VertexCount()) + if ( bSymmetryIsSet || 3 * marked_vertex_count >= subd.VertexCount() ) { subd.ClearEvaluationCache(); } @@ -15453,7 +16522,7 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( } subd.UpdateEdgeSectorCoefficients(true); } - const_cast(subd).ChangeGeometryContentSerialNumberForExperts(false); + const_cast(subd).ChangeGeometryContentSerialNumberForExperts(bChangePreservesSymmetry); } return marked_vertex_count; @@ -15559,6 +16628,7 @@ unsigned int ON_SubD::ExtrudeComponents( return ExtrudeComponents(xform, ci_list, ci_count, bExtrudeBoundaries, bPermitNonManifoldEdgeCreation); } + unsigned int ON_SubD::ExtrudeComponents( const ON_Xform& xform, const ON_COMPONENT_INDEX* ci_list, @@ -17074,6 +18144,8 @@ unsigned int ON_SubD::Internal_ExtrudeComponents( IsValid(); #endif + this->ChangeGeometryContentSerialNumberForExperts(false); + // number of moved faces and new faces created by extruding edges return moved_face_count + extruded_edge_count; } @@ -20233,6 +21305,445 @@ void ON_SubDComponentFilter::ClearFaceEdgeCountFilter() } +double ON_SubD::SurfacePointRadiusFromControlPointRadius(unsigned int polygon_count, double polgon_radius) +{ + for (;;) + { + if (polygon_count < 3) + break; + if (false == ON_IsValid(polgon_radius)) + break; + const double a = ON_2PI / ((double)polygon_count); + ON_2dPoint cv[4] = { + ON_2dPoint(1,0), + ON_2dPoint(cos(a),sin(a)), + ON_2dPoint(cos(2 * a),sin(2 * a)), + ON_2dPoint(cos(3 * a),sin(3 * a)) + }; + double k[6] = { -2,-1,0,1,2,3 }; + ON_NurbsCurve c; + c.m_dim = 2; + c.m_order = 4; + c.m_cv_count = 4; + c.m_cv = &cv[0].x; + c.m_cv_stride = (int)(&cv[1].x - &cv[0].x); + c.m_knot = k; + const ON_3dPoint p = c.PointAt(0.0); + const double r = ON_2dPoint(p.x, p.y).DistanceTo(ON_2dPoint::Origin); + if (r > 0.0) + return polgon_radius * r; + break; + } + return ON_DBL_QNAN; +} + + +double ON_SubD::ControlPointRadiusFromSurfacePointRadius(unsigned int polygon_count, double surface_radius) +{ + for (;;) + { + if (false == ON_IsValid(surface_radius)) + break; + const double r = ON_SubD::SurfacePointRadiusFromControlPointRadius(polygon_count, 1.0); + if (r > 0.0) + return surface_radius / r; + break; + } + return ON_DBL_QNAN; +} + +ON_SubDFace* ON_SubD::FindOrAddFace( + ON_SubDEdgeTag new_edge_tag, + const ON_SubDVertex* face_vertices[], + size_t vertex_count +) +{ + if (nullptr == face_vertices) + return ON_SUBD_RETURN_ERROR(nullptr); + if (vertex_count < 3) + return ON_SUBD_RETURN_ERROR(nullptr); + if (vertex_count > (size_t)ON_SubDFace::MaximumEdgeCount) + return ON_SUBD_RETURN_ERROR(nullptr); + + + // Mkae sure v[] has vertex_count unique non-null vertices. + for (unsigned i = 0; i < vertex_count; ++i) + { + if (nullptr == face_vertices[i]) + return ON_SUBD_RETURN_ERROR(nullptr); + for (unsigned j = i + 1; j < vertex_count; ++j) + { + if (face_vertices[i] == face_vertices[j]) + return ON_SUBD_RETURN_ERROR(nullptr); + } + } + + ON_SimpleArray eptrs(vertex_count); + ON_SimpleArray faces(4); + ON_SimpleArray faces_to_keep(4); + const ON_SubDVertex* ev[2] = { nullptr, face_vertices[0] }; + for (unsigned i = 0; i < vertex_count; ++i) + { + ev[0] = ev[1]; + ev[1] = face_vertices[(i + 1) % vertex_count]; + ON_SubDEdgePtr eptr = ON_SubDEdge::FromVertices(ev[0], ev[1]); + if (eptr.IsNull()) + { + // need to create this edge + ON_SubDEdge* e = this->AddEdge(new_edge_tag, const_cast(ev[0]), const_cast(ev[1])); + if (nullptr == e) + return ON_SUBD_RETURN_ERROR(nullptr); + eptr = ON_SubDEdgePtr::Create(e, 0); + faces.SetCount(0); + } + else + { + const ON_SubDEdge* e = eptr.Edge(); + if (nullptr == e) + return ON_SUBD_RETURN_ERROR(nullptr); + if (0 == e->m_face_count) + faces.SetCount(0); + else if (0 == i || faces.Count() > 0) + { + faces_to_keep.SetCount(0); + const ON_SubDFacePtr* fptr = e->m_face2; + for (unsigned short efi = 0; efi < e->m_face_count; ++efi, ++fptr) + { + if (2 == efi) + { + fptr = e->m_facex; + if (nullptr == fptr) + break; + } + const ON_SubDFace* f = ON_SUBD_FACE_POINTER(fptr->m_ptr); + if (nullptr == f) + continue; + if (0 == i) + faces_to_keep.Append(f); + else + { + for (unsigned j = 0; j < faces.UnsignedCount(); ++j) + { + if (f != faces[j]) + continue; + // every edge so far is attached to f + faces_to_keep.Append(f); + break; + } + } + } + faces = faces_to_keep; + } + } + eptrs.Append(eptr); + } + + if (eptrs.UnsignedCount() != vertex_count) + return ON_SUBD_RETURN_ERROR(nullptr); + + ON_SubDFace* new_face = this->AddFace(eptrs); + + return new_face; +} + +ON_SubD* ON_SubD::CreateCylinder( + const ON_Cylinder& cylinder, + unsigned circumference_face_count, + unsigned height_face_count, + ON_SubDEndCapStyle end_cap_style, + ON_SubDEdgeTag end_cap_edge_tag, + ON_SubDComponentLocation radius_location, + ON_SubD* destination_subd +) +{ + if (nullptr != destination_subd) + *destination_subd = ON_SubD::Empty; + + if (false == cylinder.IsValid()) + { + ON_SUBD_ERROR("Invalid cylinder parameter."); + return nullptr; + } + + if (circumference_face_count < 3) + { + ON_SUBD_ERROR("Invalid circumference_face_count parameter."); + return nullptr; + } + + if (height_face_count < 1) + { + ON_SUBD_ERROR("Invalid height_face_count parameter."); + return nullptr; + } + + const double r = cylinder.circle.Radius(); + if (false == (r > 0.0 && r < ON_UNSET_POSITIVE_VALUE)) + { + ON_SUBD_ERROR("Invalid cylinder radius parameter."); + return nullptr; + } + + //////////////////////////////////////////// + // Validate and sanitize end_cap_style parameter + // + switch (end_cap_style) + { + case ON_SubDEndCapStyle::Unset: + end_cap_style = ON_SubDEndCapStyle::None; + break; + + case ON_SubDEndCapStyle::None: + break; + + case ON_SubDEndCapStyle::Triangles: + if (circumference_face_count < 2) + end_cap_style = ON_SubDEndCapStyle::None; + else if (circumference_face_count <= 3) + end_cap_style = ON_SubDEndCapStyle::Ngon; // single triangle + break; + + case ON_SubDEndCapStyle::Quads: + if (circumference_face_count < 2) + end_cap_style = ON_SubDEndCapStyle::None; + else if (circumference_face_count <= 4) + end_cap_style = ON_SubDEndCapStyle::Ngon; // single quad or single triangle + else if (0 != (circumference_face_count%2)) + end_cap_style = ON_SubDEndCapStyle::Triangles; // must have even number of sized for a multi-quad cap. + break; + + case ON_SubDEndCapStyle::Ngon: + if (circumference_face_count < 2) + end_cap_style = ON_SubDEndCapStyle::None; + break; + + default: + end_cap_style = ON_SubDEndCapStyle::None; + break; + } + const bool bCapEnds = ON_SubDEndCapStyle::None != end_cap_style; + + /////////////////////////////////////////////// + // If cylinder in infinite, choose a height that makes the faces squarish. + // + const double height = cylinder.IsFinite() ? cylinder.Height() : (ON_2PI*r/((double)circumference_face_count))*((double)height_face_count); + if ( false == (ON_IsValid(height) && 0.0 != height) ) + { + ON_SUBD_ERROR("Invalid cylinder or count parameters."); + return nullptr; + } + + ///////////////////////////////////////////// + // H = vector that translates a ring of vertices / edges from one circumference to the next. + const ON_3dVector H = (height / ((double)height_face_count)) * cylinder.Axis().UnitVector(); + + ///////////////////////////////////////////// + // Adjust radius so result has surface and control net in the correct location. + // + ON_Circle point_generator(cylinder.IsFinite() ? cylinder.CircleAt(cylinder.height[0]) : cylinder.circle); + point_generator.radius = (ON_SubDComponentLocation::Surface == radius_location) ? ON_SubD::ControlPointRadiusFromSurfacePointRadius(circumference_face_count, r) : r; + + + ////////////////////////////////////////////// + // circumference_points[] = ring of control point locations around the cylinder's base. + // + ON_SimpleArray circumference_points(circumference_face_count); + for (unsigned i = 0; i < circumference_face_count; ++i) + { + const double a = ON_Interval::ZeroToTwoPi.ParameterAt(((double)i) / ((double)circumference_face_count)); + const ON_3dPoint P = point_generator.PointAt(a); + circumference_points.Append(P); + } + + ///////////////////////////////////////////// + // center[2] = cap centers (if needed). + const ON_3dPoint center[2] = { point_generator.Center(), point_generator.Center() + (((double)height_face_count) * H) }; + + ON_SubD* subd = (nullptr != destination_subd) ? destination_subd : new ON_SubD(); + + // v00 = 1st vertex in the previous ring of vertices / edges + ON_SubDVertex* v00 = nullptr; + for (unsigned j = 0; j <= height_face_count; ++j) + { + // add a new ring of vertices / edges + + // v0 = 1st vertex in this ring of vertices / edges + ON_SubDVertex* v0 = nullptr; + ON_SubDVertex* ev[2] = {}; + + const ON_SubDVertexTag vtag + = (false == bCapEnds || ON_SubDEdgeTag::Crease == end_cap_edge_tag) && (0 == j || j == height_face_count) + ? ON_SubDVertexTag::Crease + : ON_SubDVertexTag::Smooth; + const ON_SubDEdgeTag etag = (ON_SubDVertexTag::Crease == vtag) ? ON_SubDEdgeTag::Crease : ON_SubDEdgeTag::Smooth; + + + for (unsigned i = 0; i < circumference_face_count; ++i) + { + const ON_3dPoint P = circumference_points[i]; + circumference_points[i] = P + H; // move circumference_points[i] up to next ring + + ev[0] = ev[1]; + ev[1] = subd->AddVertex(vtag, P); + if (0 == i) + v0 = ev[1]; + else + subd->AddEdge(etag, ev[0], ev[1]); + } + subd->AddEdge(etag, ev[1], v0); + + if (j > 0 && nullptr != v00 && nullptr != v0) + { + // add a new ring of faces + + const ON_SubDVertex* fv[4] = { + nullptr, + v00, // 1st vertex in bottom face edge ring + v0, // 1st vertex in top face edge ring + nullptr + }; + for (unsigned i = 0; i < circumference_face_count; ++i) + { + const bool bLastFaceInThisRing = (i+1 == circumference_face_count); + // shift to next face's corners + fv[0] = fv[1]; + fv[3] = fv[2]; + fv[1] = bLastFaceInThisRing ? v00 : fv[0]->m_next_vertex; + fv[2] = bLastFaceInThisRing ? v0 : fv[3]->m_next_vertex; + + // make a new face + subd->FindOrAddFace(ON_SubDEdgeTag::Smooth, fv, 4); + } + } + + v00 = v0; + } + + if (bCapEnds) + { + ON_SimpleArray bdry(circumference_face_count); + const ON_SubDVertex* last_wall_vertex = subd->LastVertex(); + const ON_SubDEdge* last_wall_edge = subd->LastEdge(); + + const bool bCapHasCenterVertex = ON_SubDEndCapStyle::Triangles == end_cap_style || ON_SubDEndCapStyle::Quads == end_cap_style; + + for (unsigned enddex = 0; enddex < 2; ++enddex) + { + bdry.SetCount(0); + const ON_SubDVertex* v0 = (0 == enddex) ? const_cast(subd->FirstVertex()) : v00; + const ON_SubDVertex* ev[2] = { nullptr, v0 }; + for (unsigned i = 0; i < circumference_face_count; ++i) + { + ev[0] = ev[1]; + ev[1] = (i + 1 < circumference_face_count) ? const_cast(ev[1]->m_next_vertex) : v0; + const ON_SubDEdgePtr eptr = ON_SubDEdge::FromVertices(ev[0], ev[1]); + if (eptr.IsNull()) + break; + bdry.Append(eptr); + } + if (circumference_face_count != bdry.UnsignedCount()) + break; + + if (0 == enddex) + ON_SubDEdgeChain::ReverseEdgeChain(bdry); + + const ON_SubDVertex* fv[4] = {}; + if (bCapHasCenterVertex) + { + // fv[0] = vertex at the center of the cap + fv[0] = subd->AddVertex(ON_SubDVertexTag::Smooth, center[enddex]); + if (nullptr == fv[0]) + break; + } + + switch (end_cap_style) + { + case ON_SubDEndCapStyle::Unset: + break; + + case ON_SubDEndCapStyle::None: + break; + + case ON_SubDEndCapStyle::Triangles: + if (nullptr != fv[0]) + { + // radial triangles around the center vertex + fv[2] = bdry[0].RelativeVertex(0); + for (unsigned i = 0; i < circumference_face_count; ++i) + { + fv[1] = fv[2]; + fv[2] = bdry[i].RelativeVertex(1); + subd->FindOrAddFace(ON_SubDEdgeTag::Smooth, fv, 3); + } + } + break; + + case ON_SubDEndCapStyle::Quads: + if (nullptr != fv[0]) + { + // radial quads around the center vertex + fv[3] = bdry[0].RelativeVertex(0); + for (unsigned i = 0; i < circumference_face_count; i += 2) + { + fv[1] = fv[3]; + fv[2] = bdry[i].RelativeVertex(1); + fv[3] = bdry[(i+1)% circumference_face_count].RelativeVertex(1); + subd->FindOrAddFace(ON_SubDEdgeTag::Smooth, fv, 4); + } + } + break; + + case ON_SubDEndCapStyle::Ngon: + // cap = single n-gon + subd->AddFace(bdry); + break; + default: + break; + } + } + + if (bCapHasCenterVertex) + { + // vertices and edges added inside the caps are smooth. + for (const ON_SubDVertex* v = (nullptr != last_wall_vertex) ? last_wall_vertex->m_next_vertex : nullptr; nullptr != v; v = v->m_next_vertex) + const_cast(v)->m_vertex_tag = ON_SubDVertexTag::Smooth; + for (const ON_SubDEdge* e = (nullptr != last_wall_edge) ? last_wall_edge->m_next_edge : nullptr; nullptr != e; e = e->m_next_edge) + const_cast(e)->m_edge_tag = ON_SubDEdgeTag::Smooth; + } + } + + if (nullptr != subd) + subd->UpdateAllTagsAndSectorCoefficients(true); + + return subd; +} + +bool ON_Symmetry::SetSymmetricObject(const ON_SubD* subd) const +{ + const ON_SubDimple* subdimple = (nullptr != subd) ? subd->SubDimple() : nullptr; + return SetSymmetricObject(subdimple); +} + +bool ON_Symmetry::SetSymmetricObject(const ON_SubDimple* subdimple) const +{ + bool rc; + + if (nullptr != subdimple && this->IsSet()) + { + m_symmetric_object_content_serial_number = subdimple->GeometryContentSerialNumber(); + m_symmetric_object_topology_hash = subdimple->SubDHash(ON_SubDHashType::Topology, false).SubDHash(); + m_symmetric_object_geometry_hash = subdimple->SubDHash(ON_SubDHashType::Geometry, false).SubDHash(); + rc = true; + } + else + { + ClearSymmetricObject(); + rc = false; + } + + return rc; +} + #if defined(ON_SUBD_CENSUS) ////////////////////////////////////////////////////////////////////////// diff --git a/opennurbs_subd.h b/opennurbs_subd.h index f13b3fdb..0571afef 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -301,7 +301,7 @@ enum class ON_SubDEdgeTag : unsigned char #pragma region RH_C_SHARED_ENUM [ON_SubDHashType] [Rhino.Geometry.SubDHashType] [byte] /// -/// ON_SubDHashType used used to specify what set of information is hashed (topology or geometry). +/// ON_SubDHashType used used to specify what type of SubD information is hashed (topology or geometry). /// enum class ON_SubDHashType : unsigned char { @@ -312,17 +312,23 @@ enum class ON_SubDHashType : unsigned char Unset = 0, /// - /// A topology hash includes component ids, edge tags, and all topological relationships - /// between vertices, edges, and faces. Note that edge tags are not a mathematically topological - /// attribute, however creased edges divide the SubD into strong visual regions and change - /// what many contexts consider to be the connected components. - /// A topology hash ignores vertex control net points. + /// The Topology hash includes component ids, and all topological relationships + /// between vertices, edges, and faces. If two SubDs have the same topology hash, + /// then the the have identical labeled control net topology. /// - Topology = 1, + Topology = 3, /// - /// A geometry hash includes all information in a toplogy hash. - /// In addition, a geometry hash includes vertex tags, vertex control net points, + /// The TopologyAndEdgeCreases includes all information in a Topology hash. + /// The TopologyAndEdgeCreases adds edge crease/smooth attributes. + /// Many contexts, including face packing and exploding, take edge creases + /// into account when partitioning a SubD into regions. + /// + TopologyAndEdgeCreases = 1, + + /// + /// A geometry hash includes all information in a TopologyAndEdgeCreases hash. + /// The Geometry hash adds vertex tags, vertex control net points, /// and nonzero subdivision displacements on vertices, edges, and faces. /// If two SubDs have the same geometry hash, then they have identical surface geometry. /// @@ -373,6 +379,8 @@ public: /// static const ON_SubDHash Create(ON_SubDHashType hash_type, const class ON_SubD& subd ); + static const ON_SubDHash Create(ON_SubDHashType hash_type, const class ON_SubDimple* subdimple); + /// /// Dictionary compare of VertexCount(), EdgeCount(), FaceCount(), VertexHash(), EdgeHash(), and FaceHash() in that order. /// @@ -404,38 +412,51 @@ public: ON_SubDHashType HashType() const; /// - /// Returns: - /// If this hash was created from an ON_SubD, then the value of subd.RuntimeSerialNumber() is returned. - /// Otherwise, 0 is returned. + /// The runtime serial number can be used to identify the SubD that was hashed to created this ON_SubDHash. /// - /// + /// + /// If this hash was created from an ON_SubD, then the value of subd.RuntimeSerialNumber() is returned. + /// Otherwise, 0 is returned. + /// ON__UINT64 SubDRuntimeSerialNumber() const; /// + /// The geometry content serial number can be used to quickly determine if a SubD is exactly the instance used + /// to create this ON_SubDHash. If the geometry content serial numbers are equal, then the SubD is identical + /// to the one use to create the hash. If the geometry content serial numbers differ, the current SubD hashes + /// need to be checked against this to see what, if anything, changed. For example, moving a vertex will not + /// change a topology hash. + /// + /// /// Returns: /// If this hash was created from an ON_SubD, then the value of subd.GeometryContentSerialNumber() at the /// at the instant this hash was created is returned. /// Otherwise, 0 is returned. - /// - /// + /// ON__UINT64 SubDGeometryContentSerialNumber() const; /// - /// Saved value of subd.VertexCount(HashType()). + /// Copied from the SubD when the hash is created. /// - /// + /// + /// Number of hashed vertices. + /// unsigned int VertexCount() const; /// - /// Saved value of subd.EdgeCount(HashType()). + /// Copied from the SubD when the hash is created. /// - /// + /// + /// Number of hashed edges. + /// unsigned int EdgeCount() const; /// - /// Saved value of subd.FaceCount(HashType()). + /// Copied from the SubD when the hash is created. /// - /// + /// + /// Number of hashed faces. + /// unsigned int FaceCount() const; /// @@ -643,6 +664,13 @@ public: bool IsNull() const; bool IsNotNull() const; + /* + Returns: + If Vertex() is not nullptr, Vertex()->m_id is returned. + Otherwise, 0 is returned. + */ + unsigned int VertexId() const; + /* Returns: True if this vertex is active in its parent subd or other @@ -868,6 +896,16 @@ public: int relative_vertex_index ) const; + bool RelativeVertexMark( + int relative_vertex_index, + bool missing_vertex_return_value + ) const; + + ON__UINT8 RelativeVertexMarkBits( + int relative_vertex_index, + ON__UINT8 missing_vertex_return_value + ) const; + const ON_Line RelativeControlNetLine() const; const ON_3dVector RelativeControlNetDirection() const; @@ -1255,9 +1293,33 @@ public: */ bool IsActive() const; + /* + Returns: + True if this component is marked as a primary motif component. + Remarks: + You must use ON_SubD SymmetrySet memeber functions to get symmetry set contents. + */ + bool IsSymmetrySetPrimaryMotif() const; + + /* + Returns: + True if this component is marked being in a symmetry set. + Remarks: + You must use ON_SubD SymmetrySet memeber functions to get symmetry set contents. + */ + bool InSymmetrySet() const; + ON_SubDComponentPtr::Type ComponentType() const; class ON_SubDComponentBase* ComponentBase() const; + + /* + type_filter - [in] + If is ON_SubDComponentPtr::Type::Unset, then any type of component will be returned. + Otherwise only a component of the specified type will be returned. + */ + class ON_SubDComponentBase* ComponentBase(ON_SubDComponentPtr::Type type_filter) const; + class ON_SubDVertex* Vertex() const; class ON_SubDEdge* Edge() const; class ON_SubDFace* Face() const; @@ -1513,6 +1575,161 @@ public: ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; #endif +/* +Description: + ON_SubDComponentTest is used in contexts where custom testing or filtering of + SubD components is required. +*/ +class ON_CLASS ON_SubDComponentTest +{ +public: + ON_SubDComponentTest() = default; + virtual ~ON_SubDComponentTest(); + ON_SubDComponentTest(const ON_SubDComponentTest&) = default; + ON_SubDComponentTest& operator=(const ON_SubDComponentTest&) = default; + + /* + Sets m_ptr=ptr + */ + ON_SubDComponentTest(ON__UINT_PTR ptr); + + /* + Description: + Typically, a derived class overrides this function, uses it to inspect vertex properties, + and returns true or false. + Parameters: + v - [in] vertex being tested. + Returns: + true if the vertex "passes" the test. + false if the vertex "fails" the text. + Remarks: + The default implementation returns (cptr.IsNotNull() && 0 != m_ptr); + */ + virtual bool Passes(const ON_SubDComponentPtr cptr) const; + + /* + Returns: + this->Passes(nullptr != v ? v->ComponentPtr() : ON_SubDComponentPtr::Null); + */ + bool Passes(const class ON_SubDVertex* v) const; + + /* + Returns: + this->Passes(nullptr != e ? e->ComponentPtr() : ON_SubDComponentPtr::Null); + */ + bool Passes(const class ON_SubDEdge* e) const; + + /* + Returns: + this->Passes(nullptr != f ? f->ComponentPtr() : ON_SubDComponentPtr::Null); + */ + bool Passes(const class ON_SubDFace* f) const; + + // Passes() returns true for every non nullptr component. + static const ON_SubDComponentTest AllPass; + + // Passes() returns false for every component. + static const ON_SubDComponentTest AllFail; + +protected: + // classes derived from ON_SubDVertexFilter may use m_ptr as they see fit including to completely ignore it. + ON__UINT_PTR m_ptr = 0; +}; + +class ON_CLASS ON_SubDComponentId +{ +public: + + // type = unset and id = 0; + static const ON_SubDComponentId Unset; + + ON_SubDComponentId() = default; + ~ON_SubDComponentId() = default; + ON_SubDComponentId(const ON_SubDComponentId&) = default; + ON_SubDComponentId& operator=(const ON_SubDComponentId&) = default; + + ON_SubDComponentId(ON_SubDComponentPtr::Type component_type, unsigned int component_id); + ON_SubDComponentId(ON_SubDComponentPtr cptr); + ON_SubDComponentId(const class ON_SubDVertex* v); + ON_SubDComponentId(const class ON_SubDEdge* e); + ON_SubDComponentId(const class ON_SubDFace* f); + + static int CompareTypeAndId(const ON_SubDComponentId& lhs, const ON_SubDComponentId& rhs); + static int CompareTypeAndIdFromPointer(const ON_SubDComponentId* lhs, const ON_SubDComponentId* rhs); + + unsigned int ComponentId() const; + ON_SubDComponentPtr::Type ComponentType() const; + + /* + Returns: + true if type is not unset and id > 0 + */ + bool IsSet() const; + +private: + unsigned int m_id = 0; + ON_SubDComponentPtr::Type m_type = ON_SubDComponentPtr::Type::Unset; + unsigned char m_reserved1 = 0; + unsigned short m_reserved2 = 0; +}; + +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + +class ON_CLASS ON_SubDComponentIdList : public ON_SubDComponentTest +{ +public: + ON_SubDComponentIdList() = default; + ~ON_SubDComponentIdList() = default; + ON_SubDComponentIdList(const ON_SubDComponentIdList&) = default; + ON_SubDComponentIdList& operator=(const ON_SubDComponentIdList&) = default; + + /* + Returns: + returns InListPassesResult() when cptr id is in the list. + returns !InListPassesResult() when cptr id is not the list. + Remarks: + AddId is safe to use in multithreaded contexts. + */ + bool Passes(const ON_SubDComponentPtr cptr) const override; + + void SetInListPassesResult(bool bInListPassesResult); + + // Value Passes(cptr) returns when cptr is in the list + bool InListPassesResult() const; + + void AddId(ON_SubDComponentId cid); + + void AddId(ON_SubDComponentPtr cptr); + + /* + Parameters: + cid - in + Returns: + true if cid is in this list. + */ + bool InList(ON_SubDComponentId cid) const; + + /* + Parameters: + cptr - in + Returns: + true if cptr's id is in this list. + */ + bool InList(ON_SubDComponentPtr cptr) const; + +private: + unsigned int m_reserved1 = 0; +private: + unsigned short m_reserved2 = 0; +private: + bool m_bInListPassesResult = true; +private: + mutable bool m_bSorted = false; + mutable ON_SimpleArray m_id_list; +}; + class ON_CLASS ON_SubDComponentPtrPair { public: @@ -1666,6 +1883,209 @@ private: ON_SubDComponentPtrPairHashTable& operator=(const ON_SubDComponentPtrPairHashTable&) = delete; }; +/// +/// Simple arrays of ON_SubD_ComponentIdTypeAndTag elements are used to save +/// original tag values so the can be retrieved after complex editing operations. +/// +class ON_CLASS ON_SubD_ComponentIdTypeAndTag +{ +public: + static const ON_SubD_ComponentIdTypeAndTag Unset; + + /* + Returns: + If v is no nullptr and v->m_id > 0, a ON_SubD_ComponentIdTypeAndTag with VertexTag() = v->m_vertex_tag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromVertex(const class ON_SubDVertex* v); + + /* + Returns: + If vertex_id > 0, a ON_SubD_ComponentIdTypeAndTag with VertexTag() = vtag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromVertexId(unsigned vertex_id, ON_SubDVertexTag vtag); + + /* + Parameters: + e - [in] + If e->m_edge_tag is ON_SubDEdgeTag::SmoothX, the ON_SubD_ComponentIdTypeAndTag EdgeTag() will be ON_SubDEdgeTag::Smoooth. + Returns: + If e is not nullptr and e->m_id > 0, a ON_SubD_ComponentIdTypeAndTag with EdgeTag() = e->m_edge_tag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromEdge(const class ON_SubDEdge* e); + + + /* + Parameters: + edge_id - [in] + etag - [in] + If etag is ON_SubDEdgeTag::SmoothX, the ON_SubD_ComponentIdTypeAndTag EdgeTag() will be ON_SubDEdgeTag::Smoooth. + Returns: + If edge_id > 0, a ON_SubD_ComponentIdTypeAndTag with EdgeTag() = etag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromEdgeId(unsigned edge_id, ON_SubDEdgeTag etag); + + + /* + Parameters: + f - [in] + ftag - [in] + Any value and the interpretation is up to the context using the ON_SubD_ComponentIdTypeAndTag. + Returns: + If f is no nullptr and f->m_id > 0, a ON_SubD_ComponentIdTypeAndTag with FaceTag() = ftag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + Remarks: + SubD faces do not have a tag in the sense that vertices and edges do, but in complex editing tasks + it is sometimes useful to include faces in an array of ON_SubD_ComponentIdTypeAndTag elements. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromFace(const class ON_SubDFace* f, unsigned char ftag); + + /* + Parameters: + face_id - [in] + ftag - [in] + Any value and the interpretation is up to the context using the ON_SubD_ComponentIdTypeAndTag. + Returns: + If face_id > 0, a ON_SubD_ComponentIdTypeAndTag with FaceTag() = vtag is returned. + Otherwise ON_SubD_ComponentIdTypeAndTag::Unset is returned. + Remarks: + SubD faces do not have a tag in the sense that vertices and edges do, but in complex editing tasks + it is sometimes useful to include faces in an array of ON_SubD_ComponentIdTypeAndTag elements. + */ + static const ON_SubD_ComponentIdTypeAndTag CreateFromFaceId(unsigned face_id, unsigned char ftag); + + /* + Description: + Dictionary compare on ComponentType() and ComponentId() in that order. + */ + static int CompareTypeAndId(const ON_SubD_ComponentIdTypeAndTag* lhs, const ON_SubD_ComponentIdTypeAndTag* rhs); + + /* + Description: + Dictionary compare on ComponentType(), ComponentId(), and tag in that order. + */ + static int CompareTypeAndIdAndTag(const ON_SubD_ComponentIdTypeAndTag* lhs, const ON_SubD_ComponentIdTypeAndTag* rhs); + + /* + Parameters: + v - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If v is in sorted_tags[], the VertexTag() from from sorted_tags[] is returned. + Otherwise v->m_vertex_tag is returned. + */ + static ON_SubDVertexTag OriginalVertexTag( + const class ON_SubDVertex* v, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + /* + Parameters: + vertex_id - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If vertex_id is in sorted_tags[], the VertexTag() from from sorted_tags[] is returned. + Otherwise ON_SubDVertexTag::Unset is returned. + */ + static ON_SubDVertexTag OriginalVertexTag( + unsigned vertex_id, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + /* + Parameters: + e - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If e is in sorted_tags[], the EdgeTag() from from sorted_tags[] is returned. + Otherwise e->m_edge_tag is returned. + */ + static ON_SubDEdgeTag OriginalEdgeTag( + const class ON_SubDEdge* e, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + /* + Parameters: + edge_id - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If edge_id is in sorted_tags[], the EdgeTag() from from sorted_tags[] is returned. + Otherwise ON_SubDEdgeTag::Unset is returned. + */ + static ON_SubDEdgeTag OriginalEdgeTag( + unsigned edge_id, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + /* + Parameters: + f - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If f is in sorted_tags[], the FaceTag() from from sorted_tags[] is returned. + Otherwise 0 is returned. + */ + static unsigned char OriginalFaceTag( + const class ON_SubDFace* f, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + /* + Parameters: + face_id - [in] + sorted_tags[] - [in] + Array sorted by ON_SubD_ComponentIdTypeAndTag::CompareTypeAndId(). + Returns: + If face_id is in sorted_tags[], the FaceTag() from from sorted_tags[] is returned. + Otherwise ON_SubDFaceTag::Unset is returned. + */ + static unsigned char OriginalFaceTag( + unsigned face_id, + const ON_SimpleArray< ON_SubD_ComponentIdTypeAndTag>& sorted_tags + ); + + ON_SubDComponentPtr::Type ComponentType() const; + + ON_SubDVertexTag VertexTag() const; + + unsigned VertexId() const; + + /* + Returns: + ON_SubDEdgeTag::Unset, ON_SubDEdgeTag::Smooth, or ON_SubDEdgeTag::Crease. + */ + ON_SubDEdgeTag EdgeTag() const; + + unsigned EdgeId() const; + + unsigned char FaceTag() const; + + unsigned FaceId() const; + + const ON_wString ToString() const; + +private: + ON_SubDComponentPtr m_cptr = ON_SubDComponentPtr::Null; + unsigned m_id = 0; + ON_SubDComponentPtr::Type m_type = ON_SubDComponentPtr::Type::Unset; + unsigned char m_tag = 0; + unsigned char m_bits = 0; +}; + +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + + ////////////////////////////////////////////////////////////////////////// // // ON_SubDSectorId @@ -2451,6 +2871,63 @@ private: }; +#pragma region RH_C_SHARED_ENUM [ON_SubDEndCapStyle] [Rhino.Geometry.SubDEndCapStyle] [byte] +/// +/// ON_SubDEndCapStyle enumerates the type of end caps functions like ON_SubD::CreateCylinder will create. +/// Use ON_SubDEndCapStyleFromUnsigned(integer value) to convert integer values to an ON_SubDEndCapStyle. +/// Use ON_SubDEndCapStyleToString(end_cap_style) to convert ON_SubDEndCapStyle values to string descriptions. +/// +enum class ON_SubDEndCapStyle : unsigned char +{ + /// + /// Used to indicate the end cap style is not set. + /// + Unset = 0, + + /// + /// Ends are not capped. + /// + None = 1, + + /// + /// Ends are capped with triangles. + /// + Triangles = 2, + + /// + /// When the end has an even number of edges, is will be capped with quads. Otherwise it will be capped with triangles. + /// + Quads = 3, + + /// + /// Ends are capped with a n-gon. This is a poor choice when there are a large number of edges in the end boundary. + /// + Ngon = 4 +}; +#pragma endregion + + +/// +/// Convert an integer value to an ON_SubDEndCapStyle enum value. +/// +/// +/// If subd_cap_style_as_unsigned is not valid, then ON_SubDEndCapStyle::Unset is returned. +/// +ON_DECL +ON_SubDEndCapStyle ON_SubDEndCapStyleFromUnsigned( + unsigned int subd_cap_style_as_unsigned +); + +/// +/// Convert subd_cap_style to a string description. +/// +/// +/// +ON_DECL +const ON_wString ON_SubDEndCapStyleToString( + ON_SubDEndCapStyle subd_cap_style +); + ////////////////////////////////////////////////////////////////////////// // // ON_SubD @@ -2538,7 +3015,7 @@ public: per face materials, texture coordinates, and texture mappings. Remarks: Changing the geometry content serial number automatically changes - the render content serial number. If you call ChangeGeometryContentSerialNumber(), + the render content serial number. If you call ChangeGeometryContentSerialNumberForExperts(), there is no need to also call ChangeRenderContentSerialNumber(). */ ON__UINT64 ChangeRenderContentSerialNumber() const; @@ -2563,6 +3040,29 @@ public: bool bForceUpdate ) const; + /* + Description: + If two SubDs have identical values of GeometryHash(), then they have + identical surface geometry. + Returns: + this->SubDHash(ON_SubDHashType::Geometry,false).SubDHash(). + Remarks: + If the geometry hashes are equal, the topology hashes are equal. + */ + const ON_SHA1_Hash GeometryHash() const; + + /* + Description: + If two SubDs have identical values of TopologyHash(), then they have + identical labeled control net topology. The labels are the vertex, edge, + and face ids. + Returns: + this->SubDHash(ON_SubDHashType::Topology,false).SubDHash(). + Remarks: + Two SubDs can have the same topology hash but different geometry hashes. + */ + const ON_SHA1_Hash TopologyHash() const; + /* Description: Get the SubD appearance (surface or control net); @@ -2594,6 +3094,18 @@ public: static const ON_SubDComponentLocation DefaultSubDAppearance; // = ON_SubDComponentLocation::Surface static const ON_SubDTextureCoordinateType DefaultTextureCoordinateType; // = ON_SubDTextureCoordinateType::Packed + +public: + static ON_SubD* CreateCylinder( + const class ON_Cylinder& cylinder, + unsigned circumference_face_count, + unsigned height_face_count, + ON_SubDEndCapStyle end_cap_style, + ON_SubDEdgeTag end_cap_edge_tag, + ON_SubDComponentLocation radius_location, + ON_SubD* destination_subd + ); + public: static ON_SubDVertexTag VertexTagFromUnsigned( unsigned int vertex_tag_as_unsigned @@ -3052,6 +3564,38 @@ public: ON_SimpleArray* triple_knots ); + /* + Parameters: + point_count - [in] >= 3 + Number of control points in a regular planar SubD ngon with creased edges. + control_point_radius - [in] + Distance from an ngon control point to the ngon center. + Returns: + Distance from an ngon surface point to the ngon center. + See Also: + ON_SubD::SurfacePointRadiusFromControlPointRadius() + */ + static double SurfacePointRadiusFromControlPointRadius( + unsigned int point_count, + double control_point_radius + ); + + /* + Parameters: + point_count - [in] >= 3 + Number of control points in a regular planar SubD ngon with creased edges. + surface_radius - [in] + Distance from an ngon surface point to the ngon center. + Returns: + Distance from an ngon control point to the ngon center. + See Also: + ON_SubD::ControlPointRadiusFromSurfacePointRadius() + */ + static double ControlPointRadiusFromSurfacePointRadius( + unsigned int point_count, + double surface_point_radius + ); + @@ -3114,6 +3658,70 @@ public: //virtual unsigned int SizeOf() const override; + /* + Description: + This is a debugging too used to study how efficiently SubDs are using memory. + Returns: + Total operating system heap (in bytes) used by this SubD's ON_FixedSizePools, inlcude the mesh fragments pool. + Remarks: + SizeOfAllElements() = SizeOfActiveElements() + SizeOfUnusedElements(). + */ + size_t SizeOfAllElements() const; + + /* + Description: + This is a debugging too used to study how efficiently SubDs are using memory. + Returns: + Total operating system heap (in bytes) of memory in this SubD's ON_FixedSizePools + that is currently used by active elements, including active mesh fragments. + Remarks: + SizeOfAllElements() = SizeOfActiveElements() + SizeOfUnusedElements(). + */ + size_t SizeOfActiveElements() const; + + /* + Description: + This is a debugging too used to study how efficiently SubDs are using memory. + Returns: + Total operating system heap (in bytes) of memory in this SubD's ON_FixedSizePools + that is reserved but not currently used, including unused mesh fragments. + Remarks: + SizeOfAllElements() = SizeOfActiveElements() + SizeOfUnusedElements(). + */ + size_t SizeOfUnusedElements() const; + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Total operating system heap memory (in bytes) used by the mesh fragments pool. + Remarks: + SizeOfAllMeshFragments() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfAllMeshFragments() const; + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Operating system heap memory (in bytes) that are used by active mesh fragments. + Remarks: + SizeOfAllMeshFragments() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfActiveMeshFragments() const; + + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Operating system heap memory (in bytes) that has been reserved for mesh fragments + but is not currently used by active mesh fragments. + Remarks: + SizeOfAllMeshFragments() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfUnusedMeshFragments() const; + //virtual ON__UINT32 DataCRC( ON__UINT32 current_remainder @@ -3573,6 +4181,33 @@ public: ON_SimpleArray& cptr_list ) const; + ///////////////////////////////////////////////////////// + // + // Membership query + // + bool InSubD(const class ON_SubDVertex* vertex) const; + bool InSubD(const class ON_SubDEdge* edge) const; + bool InSubD(const class ON_SubDFace* face) const; + + + /* + Returns: + ON_SubDComponentPtr::Type::Unset if b is not in this SubD. + Otherwise the type of the component is returned. + */ + bool InSubD(ON_SubDComponentPtr cptr) const; + + /* + Returns: + If b is in this SubD, a ON_SubDComponentPtr to b is returned. + Otherwise ON_SubDComponentPtr::Null is returned. + Remarks: + This function is the slowest of the InSubD() overrides. + When b is an unknown component type, this function can be used to + safely determine what type of component (vertex/edge/face). + */ + const ON_SubDComponentPtr InSubD(const class ON_SubDComponentBase* b) const; + ///////////////////////////////////////////////////////// // // Vertex access @@ -3587,6 +4222,8 @@ public: /* Parameters: hash_type - [in] + All hashes include vertex id. + Geometry hashes include vertex tag, vertex control point, and vertex displacement. Returns: A SHA1 hash of the SubD's vertices. */ @@ -3642,6 +4279,8 @@ public: /* Parameters: hash_type - [in] + All hashes include edge id, edge vertex ids in order, the edge crease/smooth attribute. + Geometry hashes include edge displacements. Returns: A SHA1 hash of the SubD's edges. */ @@ -3945,6 +4584,21 @@ public: bool HasPerFaceColorsFromPackId() const; + /* + Description: + If a SubD is symmetric and a face belongs to a symmetry set, then per face color is + set according to the motif the face belongs to. + Otherwise, its per face color is cleared. + */ + void SetPerFaceColorsFromSymmetryMotif() const; + + /* + Returns: + True if per face colors were set by SetPerFaceColorsFromSymmetryMotif(). + */ + bool HasPerFaceColorsFromSymmetryMotif() const; + + ///* //Description: // The ON__INT_PTRs in the tree are const ON_SubDMeshFragment* pointers. @@ -4551,7 +5205,7 @@ public: const ON_COMPONENT_INDEX* ci_list, size_t ci_count, ON_SubDComponentLocation component_location - ); + ); unsigned int TransformComponents( const ON_Xform& xform, @@ -4572,7 +5226,7 @@ public: const ON_Xform& xform, const ON_COMPONENT_INDEX* ci_list, size_t ci_count - ); + ); unsigned int ExtrudeComponents( const ON_Xform& xform, @@ -4586,7 +5240,7 @@ public: size_t ci_count, bool bExtrudeBoundaries, bool bPermitNonManifoldEdgeCreation - ); + ); unsigned int ExtrudeComponents( const ON_Xform& xform, @@ -4764,6 +5418,15 @@ public: unsigned int initial_face_capacity ); + /* + Parameters: + v - [in] + A vertex with zero edge and zero faces. + */ + bool ReturnVertexForExperts( + ON_SubDVertex* v + ); + /* Description: Search for a vertex with a specificed control net point. @@ -4976,6 +5639,15 @@ public: unsigned int initial_face_capacity ); + /* + Parameters: + e - [in] + An edge in this subd with no vertices or faces. + */ + bool ReturnEdgeForExperts( + ON_SubDEdge* e + ); + /* Parameters: edge0 - [in] @@ -5134,6 +5806,29 @@ public: unsigned int edge_count ); + /* + Description: + Finds an existing face or adds a new face with corners at face_vertices[]. + Parameters: + new_edge_tag - [in] + If an edge needs to be added, this tag is assigned to the new edge. + When in doubt, pass ON_SubDEdgeTag::Unset and call this->UpdateAllTagsAndSectorCoefficients(true) + after you are finished modifying the SubD. + face_vertices - [in] + Array of vertices at the face corners in counter-clockwise order. + face_vertex_count - [in] + Number of vertices in face_vertices[] + Returns: + If the input is not valid, nullptr is returned. + If there is a face with the specified corners exists, then it is returned. + Otherwise a new face is added and returned. + */ + class ON_SubDFace* FindOrAddFace( + ON_SubDEdgeTag new_edge_tag, + const class ON_SubDVertex* face_vertices[], + size_t face_vertex_count + ); + /* Parameters: candidate_face_id - [in] @@ -5152,6 +5847,15 @@ public: unsigned int edge_count ); + /* + Parameters: + f - [in] + A face with zero edges + */ + bool ReturnFaceForExperts( + ON_SubDFace* f + ); + /* Description: Add texture points to a face. @@ -5286,6 +5990,67 @@ public: ON_SubDEdgePtr eptr ); + + /* + Description: + Expert user tool to insert an edge in the face's edge array. + Parameters: + face - [in] + i - [in] + index where the edge should be inserted. + eptr - [in] + direction must be set correctly + bAddFaceToRelativeVertex0 - [in] + If true, face is appended to the eptr.RelativeVertex(0)'s face array. + bAddFaceToRelativeVertex1 - [in] + If true, face is appended to the eptr.RelativeVertex(0)'s face array. + Returns: + true if successful. + Remarks: + This tool is used during construction or editing of a SubD and the + connection is added even if the result is an invalid face or edge. + It is up to the expert user to make enough changes to create a valid SubD. + */ + bool AddFaceEdgeConnection( + ON_SubDFace* face, + unsigned int i, + ON_SubDEdgePtr eptr, + bool bAddbAddFaceToRelativeVertex0, + bool bAddbAddFaceToRelativeVertex1 + ); + + /* + Description: + Expert user tool to set a face's boundary. + Parameters: + face - [in] + Face that is in the subd with no edges. + edges - [in] + Array of edge_count pointers that form a loop. + Caller is responsible for insuring edges and vertices appear only + one time in the loop. + edge_count - [in] + Number of edges in the boundary. + Returns: + True if successful (all edge-face and vertex-face connections are set). + False otherwise. + Remarks: + This tool is used during construction or editing of a SubD and the + connection is added even if the result is an invalid face or edge. + It is up to the expert user to make enough changes to create a valid SubD. + */ + bool SetFaceBoundary( + ON_SubDFace* face, + const ON_SubDEdgePtr* edges, + size_t edge_count + ); + + bool SetFaceBoundary( + ON_SubDFace* face, + const ON_SimpleArray& edges + ); + + /* Description: Expert user tool to insert an edge in the face's edge array. @@ -5307,11 +6072,12 @@ public: /* Description: - Expert user tool to insert an edge in the face's edge array. + Expert user tool to remove the connection between and edge and face. Parameters: face - [in] i - [in] index where the edge should be removed. + 0 <= i < face->EdgeCount() removed_edge - [out] removed edge Remarks: @@ -5326,7 +6092,7 @@ public: /* Description: - Expert user tool to insert an edge in the face's edge array. + Expert user tool to remove the connection between and edge and face. Parameters: face - [in] i - [in] @@ -5344,6 +6110,39 @@ public: ON_SubDEdgePtr& removed_edge ); + /* + Description: + Expert user tool to remove a connection beteen an edge and vertex + Parameters: + e - [in] + An edge with zero attached faces. + v - [in] + A vertex attached to the e. + Returns: + If successful, true is returned. + Otherwise false is returned. + */ + bool RemoveEdgeVertexConnection( + ON_SubDEdge* e, + ON_SubDVertex* v + ); + + /* + Description: + Expert user tool to remove a connection beteen an edge and edge->vertex[evi] + Parameters: + e - [in] + An edge with zero attached faces. + evi - [in] + 0 or 1 specifying which vertex to remove. + Returns: + If successful, a pointer to the removed vertex is returned. + Otherwise nullptr is returned. + */ + ON_SubDVertex* RemoveEdgeVertexConnection( + ON_SubDEdge* e, + unsigned evi + ); /* Description: @@ -5813,7 +6612,10 @@ private: void CopyHelper(const ON_SubD&); - public: +private: + class ON_SubDHeap* Internal_Heap() const; + +public: /* Returns: @@ -5911,7 +6713,7 @@ public: /* Returns: - The value of ON_SubDHash::Create(ON_SubDHashType::Topology, *this) when the faces were packed. + The value of ON_SubDHash::Create(ON_SubDHashType::TopologyAndEdgeCrease, *this) when the faces were packed. */ const ON_SubDHash FacePackingTopologyHash() const; @@ -9713,11 +10515,38 @@ public: protected: void CopyBaseFrom( - const ON_SubDComponentBase* src + const ON_SubDComponentBase* src, + bool bCopySymmetrySetNext ); +public: + /* + Returns: + True if this component is marked as a primary motif component. + Remarks: + You must use ON_SubD SymmetrySet memeber functions to get symmetry set contents. + */ + bool IsSymmetrySetPrimaryMotif() const; + + /* + Returns: + True if this component is marked being in a symmetry set. + Remarks: + You must use ON_SubD SymmetrySet memeber functions to get symmetry set contents. + */ + bool InSymmetrySet() const; + private: - ON_SubDComponentPtr m_reserved; // reserved for the symmetric component when symmetry is implemnted. + // Symmetry sets are a linked loops of components order so that symmetry(component) = next component. + // There is exactly one motif in each symmetry set. + // The motif component is marked with 1 == m_symmetry_set_next.ComponentDirection(). + // The next component in the symmetry set loop is m_symmetry_set_next.Vertex()/Edge()/Face(). + // When a symmetry set is a singleton (fixed component in the symmetry), this = m_symmetry_set_next + // and m_symmetry_set_next.ComponentDirection()= 1. + // The only safe way query, set, and clear symmetry set information is to use + // ON_SubD.*SymmetrySet*() functions. Any other technique is not supported and will cause crashes. + friend class ON_SubDArchiveIdMap; + ON_SubDComponentPtr m_symmetry_set_next; }; //////////////////////////////////////////////////////////////////////////// @@ -9859,6 +10688,18 @@ public: */ void ClearSavedSubdivisionPoints() const; + + /* + Description: + Clears saved subdivision and limit surface information for this vertex. + Parameters: + bClearNeighborhood - [in] + If true, all edges and faces attached to this vertex are also cleared. + */ + void ClearSavedSubdivisionPoints( + bool bClearNeighborhood + ) const; + public: static const ON_SubDVertex Empty; @@ -10298,8 +11139,19 @@ public: ) const; /* + Description: + Count creases with specified topology. + Parameters: + bCountInteriorCreases - [in] + Count includes crease edges with 2 faces. + bCountBoundaryCreases - [in] + Count includes crease edges with 1 face. + bCountNonmanifoldCreases - [in] + Count includes crease edges with 3 or more faces. + bCountWireCreases - [in] + Count includes crease edges with 0 faces. Returns: - Number of creased edges + Number of creased edges with the specified topology. */ const unsigned int CreasedEdgeCount( bool bCountInteriorCreases, @@ -10308,6 +11160,11 @@ public: bool bCountWireCreases ) const; + /* + Returns: + Number of creased edges. + */ + const unsigned int CreasedEdgeCount() const; /* Parameters: @@ -10571,7 +11428,8 @@ private: const ON_SubDVertex* src, bool bCopyEdgeArray, bool bCopyFaceArray, - bool bCopySurfacePointList + bool bCopySurfacePointList, + bool bCopySymmetrySetNext ); }; @@ -10605,6 +11463,17 @@ public: */ void ClearSavedSubdivisionPoints() const; + /* + Description: + Clears saved subdivision and limit surface information for this edge. + Parameters: + bClearNeighborhood - [in] + If true, all vertices and faces attached to this edge are also cleared. + */ + void ClearSavedSubdivisionPoints( + bool bClearNeighborhood + ) const; + public: static const ON_SubDEdge Empty; @@ -11244,7 +12113,8 @@ private: const ON_SubDEdge* src, bool bReverseEdge, bool bCopyVertexArray, - bool bCopyFaceArray + bool bCopyFaceArray, + bool bCopySymmetrySetNext ); }; @@ -11278,6 +12148,17 @@ public: */ void ClearSavedSubdivisionPoints() const; + /* + Description: + Clears saved subdivision and limit surface information for this face. + Parameters: + bClearNeighborhood - [in] + If true, all vertices, edges and face attached to this face are also cleared. + */ + void ClearSavedSubdivisionPoints( + bool bClearNeighborhood + ) const; + public: static const ON_SubDFace Empty; @@ -11334,6 +12215,25 @@ public: const ON_BoundingBox ControlNetBoundingBox() const; + /* + Parameters: + vertex_list - [in] + vertices in face boundary. + vertex_list[0] can be any vertex in the face boundary and vertex_list[] can traverse the + boundary in order or reversed. + Return: + If there is a face whose boundary vertex list is face_vertices[], then that face is returned and + ON_SubDFacePtr.FaceDirection() indicates the orientation of face_vertices[]. + Otherwise ON_SubDFacePtr::Null is returned. + */ + static const ON_SubDFacePtr FromVertices( + const ON_SimpleArray< const ON_SubDVertex* >& vertex_list + ); + static const ON_SubDFacePtr FromVertices( + const ON_SubDVertex*const* vertex_list, + size_t face_vertices_count + ); + const ON_COMPONENT_INDEX ComponentIndex() const; const ON_SubDComponentPtr ComponentPtr() const; @@ -11895,8 +12795,33 @@ private: private: public: + /* + Description: + Returns the number of edges and (number of vertices) in the face's boundary. + Remarks: + Boundaries that vist the same vertex or same edge multiple times are not permitted. + So the number of vertices and number of edges is always the same. + */ unsigned int EdgeCount() const; + /* + Description: + Rapidly verifies that: + 1. EdgeCount() >= 3 + 2. Every edge is not null and has 2 non-null vertices. + 3. The end vertex of and edge is identical to the start vertex of the next edge. + 4. Every edge has FaceCount() >= 1. + 5. Every vertex has EdgeCount() >= 2 and FaceCount() >= 1. + 6. The first 4 edges are unique. + 6. The first 4 vertices are unique. + Returns: + True if the 5 conditions above are true. + Remarks: + The face can still be invalid, but if HasValidEdges() returns true, + it is save to deference pointers returned by the face's Edge() and Vertex() functions. + */ + bool HasEdges() const; + const ON_SubDEdgePtr EdgePtr( unsigned int i ) const; @@ -11905,6 +12830,17 @@ public: const class ON_SubDEdge* e ) const; + bool EdgeMark( + unsigned int i, + bool bMissingEgeReturnValue + ) const; + + ON__UINT8 EdgeMarkBits( + unsigned int i, + ON__UINT8 missing_edge_return_value + ) const; + + const class ON_SubDVertex* Vertex( unsigned int i ) const; @@ -11917,6 +12853,16 @@ public: const ON_SubDVertex* vertex ) const; + bool VertexMark( + unsigned int i, + bool bMissingVertexReturnValue + ) const; + + ON__UINT8 VertexMarkBits( + unsigned int i, + ON__UINT8 missing_vertex_return_value + ) const; + /* Returns; If the vertex is in this face's boundary, pair of face boundary edges at the vertex is returned @@ -12171,6 +13117,15 @@ public: bool bMark ) const; + /* + Description: + Get the face's boundary. + Parameters: + face_edge_array - [out] + The boundary of the face is returned in canonical counter-clockwise order. + Returns: + Number of edges in the face's boundary. + */ unsigned int GetEdgeArray( ON_SimpleArray< ON_SubDEdgePtr >& face_edge_array ) const; @@ -12178,7 +13133,8 @@ public: private: void CopyFrom( const ON_SubDFace* src, - bool bCopyEdgeArray + bool bCopyEdgeArray, + bool bCopySymmetrySetNext ); }; @@ -12561,7 +13517,7 @@ private: // ON_SubDVertexIdIterator // -class ON_SubDVertexIdIterator : private ON_FixedSizePoolIterator +class ON_CLASS ON_SubDVertexIdIterator : private ON_FixedSizePoolIterator { public: ON_SubDVertexIdIterator() = default; @@ -12585,6 +13541,9 @@ public: */ const ON_SubDVertex* FirstVertex(); + const ON_SubDVertex* FirstVertexOnLevel(unsigned int level_index); + + /* Description: In general, you want to use a ON_SubDVertexIterator to loop through SubD vertices. @@ -12596,6 +13555,8 @@ public: */ const ON_SubDVertex* NextVertex(); + const ON_SubDVertex* NextVertexOnLevel(unsigned int level_index); + /* Returns: The most recently returned vertex from a call to FirstVertex() or NextVertex(). @@ -12819,7 +13780,7 @@ private: // ON_SubDEdgeIdIterator // -class ON_SubDEdgeIdIterator : private ON_FixedSizePoolIterator +class ON_CLASS ON_SubDEdgeIdIterator : private ON_FixedSizePoolIterator { public: ON_SubDEdgeIdIterator() = default; @@ -12843,6 +13804,9 @@ public: */ const ON_SubDEdge* FirstEdge(); + const ON_SubDEdge* FirstEdgeOnLevel(unsigned int level_index); + + /* Description: In general, you want to use a ON_SubDEdgeIterator to loop through SubD edges. @@ -12854,6 +13818,8 @@ public: */ const ON_SubDEdge* NextEdge(); + const ON_SubDEdge* NextEdgeOnLevel(unsigned int level_index); + /* Returns: The most recently returned edge from a call to FirstEdge() or NextEdge(). @@ -13079,7 +14045,7 @@ private: // ON_SubDFaceIdIterator // -class ON_SubDFaceIdIterator : private ON_FixedSizePoolIterator +class ON_CLASS ON_SubDFaceIdIterator : private ON_FixedSizePoolIterator { public: ON_SubDFaceIdIterator() = default; @@ -13102,6 +14068,8 @@ public: The face with the smallest id. */ const ON_SubDFace* FirstFace(); + + const ON_SubDFace* FirstFaceOnLevel(unsigned int level_index); /* Description: @@ -13114,6 +14082,8 @@ public: */ const ON_SubDFace* NextFace(); + const ON_SubDFace* NextFaceOnLevel(unsigned int level_index); + /* Returns: The most recently returned face from a call to FirstFace() or NextFace(). @@ -14397,7 +15367,11 @@ public: ON_SubDComponentLocation ComponentLocation() const; /* - The interpretation of this value depends on the context. + Description: + The interpretation of this value depends on the context. + When the context is an ON_SubDComponentRef created by + CRhinoGetObject, ReferenceId() is the parent CRhinoSubDObject's + runtime serial number. */ ON__UINT_PTR ReferenceId() const; @@ -15958,6 +16932,93 @@ private: + +class ON_SubDRTree : public ON_RTree +{ +private: + ON_SubDRTree(const ON_SubDRTree&) = delete; + ON_SubDRTree& operator=(const ON_SubDRTree&) = delete; + +public: + ON_SubDRTree() = default; + ~ON_SubDRTree() = default; + +public: + + bool CreateSubDVertexRTree( + const ON_SubD& subd, + ON_SubDComponentLocation vertex_location + ); + + const ON_SubDVertex* FindVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance + ) const; + + const ON_SubDVertex* FindMarkedVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance + ) const; + + const ON_SubDVertex* FindUnmarkedVertexAtPoint( + const ON_3dPoint P, + const double distance_tolerance + ) const; + + const ON_SubD& SubD() const; + + /* + Description: + CLears the refernce to the SubD and removes all RTree nodes. + */ + void Clear(); + +private: + // Shares contents with the referenced subd. + // Used to increment the reference count on the ON_SubDimple (not a real copy). + // This is used to insure the vertex pointers in the rtree nodes are valid. + ON_SubD m_subd; +}; + +class ON_SubDRTreeVertexFinder +{ +public: + ON_SubDRTreeVertexFinder() = default; + ~ON_SubDRTreeVertexFinder() = default; + ON_SubDRTreeVertexFinder(const ON_SubDRTreeVertexFinder&) = default; + ON_SubDRTreeVertexFinder& operator=(const ON_SubDRTreeVertexFinder&) = default; + +public: + static const ON_SubDRTreeVertexFinder Create(const ON_3dPoint P); + + /* + Parameters: + bMarkFilter - [in] + Vertices with Mark = bMarkFilter are eligable to be found. + Vertices with Mark != bMarkFilter are ignored. + */ + static const ON_SubDRTreeVertexFinder Create(const ON_3dPoint P, bool bMarkFilter); + +public: + ON_3dPoint m_P = ON_3dPoint::NanPoint; + double m_distance = ON_DBL_QNAN; + const ON_SubDVertex* m_v = nullptr; + + // When m_bMarkFilterEnabled is true, then vertices with Mark() == m_bMarkFilter are eligable + // to be found and vertices with Mark() != m_bMarkFilter are ignored. + bool m_bMarkFilterEnabled = false; + bool m_bMarkFilter = false; +private: + unsigned short m_reserved1 = 0; + unsigned int m_reserved2 = 0; + +public: + + static bool Callback(void* a_context, ON__INT_PTR a_id); + + +}; + #if defined(ON_COMPILING_OPENNURBS) /* The ON_SubDAsUserData class is used to attach a subd to it proxy mesh diff --git a/opennurbs_subd_archive.cpp b/opennurbs_subd_archive.cpp index 1dc7af58..d93d8c18 100644 --- a/opennurbs_subd_archive.cpp +++ b/opennurbs_subd_archive.cpp @@ -139,6 +139,59 @@ static bool Internal_FinishWritingComponentAdditions(ON_BinaryArchive& archive) return archive.WriteChar(sz); } + +static bool Internal_WriteArchiveIdAndFlags( + unsigned int archive_id, + ON__UINT_PTR ptr_flags, + ON_BinaryArchive& archive +) +{ + if (!archive.WriteInt(archive_id)) + return ON_SUBD_RETURN_ERROR(false); + unsigned char flags = (unsigned char)ON_SUBD_COMPONENT_FLAGS(ptr_flags); + if (!archive.WriteChar(flags)) + return ON_SUBD_RETURN_ERROR(false); + return true; +} + +static bool Internal_ReadArchiveIdAndFlagsIntoComponentPtr( + ON_BinaryArchive& archive, + ON__UINT_PTR& element_ptr +) +{ + element_ptr = 0; + unsigned int archive_id = 0; + if (!archive.ReadInt(&archive_id)) + return ON_SUBD_RETURN_ERROR(false); + unsigned char flags = 0; + if (!archive.ReadChar(&flags)) + return ON_SUBD_RETURN_ERROR(false); + element_ptr = archive_id; + element_ptr *= (ON_SUBD_COMPONENT_FLAGS_MASK + 1); + element_ptr += (flags & ON_SUBD_COMPONENT_FLAGS_MASK); + return true; +} + +static bool Internal_WritesSymmetrySetNext( + const ON_SubDComponentBase& c, + ON_BinaryArchive& archive +) +{ + const ON_SubDComponentPtr symmetry_set_next = ON_SubDArchiveIdMap::SymmetrySetNextForExperts(c); + const ON_SubDComponentBase* next_c = symmetry_set_next.ComponentBase(); + const unsigned archive_id = (nullptr != next_c) ? next_c->ArchiveId() : 0; + return Internal_WriteArchiveIdAndFlags(archive_id, symmetry_set_next.m_ptr, archive); +} + + +static bool Internal_ReadSymmetrySetNext( + ON_BinaryArchive& archive, + const ON_SubDComponentBase& c +) +{ + return Internal_ReadArchiveIdAndFlagsIntoComponentPtr(archive, ON_SubDArchiveIdMap::SymmetrySetNextForExperts(c).m_ptr); +} + static bool WriteBase( const ON_SubDComponentBase* base, ON_BinaryArchive& archive @@ -206,6 +259,17 @@ static bool WriteBase( break; } + // 5 byte symmetry set next addition Dec 2020 Rhino 7.2 and later + // 5 bytes = unsigned archive id + char flags + const bool bWriteSymmetrySetNext = base->InSymmetrySet(); + if (false == Internal_WriteComponentAdditionSize(bWriteSymmetrySetNext, archive, 5)) + break; + if (bWriteSymmetrySetNext) + { + if (!Internal_WritesSymmetrySetNext(*base,archive)) + break; + } + return Internal_FinishWritingComponentAdditions(archive); } return ON_SUBD_RETURN_ERROR(false); @@ -298,6 +362,20 @@ static bool ReadBase( if (!archive.ReadInt(&base.m_group_id)) break; } + + // 5 bytes symmetry set next addition Dec 2020 Rhino 7.2 and later + // 5 bytes = unsigned archive id + char flags + sz = 0; + if (false == Internal_ReadComponentAdditionSize(archive, 5, &sz)) + break; + if (ON_SubDComponentArchiveAdditionEndMark == sz) + return true; // end of additions + if (0 != sz) + { + if (!Internal_ReadSymmetrySetNext(archive,base)) + break; + } + return Internal_FinishReadingComponentAdditions(archive); } @@ -305,42 +383,7 @@ static bool ReadBase( return ON_SUBD_RETURN_ERROR(false); } - -static bool WriteArchiveIdAndFlags( - unsigned int archive_id, - ON__UINT_PTR ptr_flags, - ON_BinaryArchive& archive - ) -{ - if (!archive.WriteInt(archive_id)) - return ON_SUBD_RETURN_ERROR(false); - unsigned char flags = (unsigned char)ON_SUBD_COMPONENT_FLAGS(ptr_flags); - if (!archive.WriteChar(flags)) - return ON_SUBD_RETURN_ERROR(false); - return true; -} - - - -static bool ReadArchiveIdAndFlagsIntoComponentPtr( - ON_BinaryArchive& archive, - ON__UINT_PTR& element_ptr - ) -{ - element_ptr = 0; - unsigned int archive_id = 0; - if (!archive.ReadInt(&archive_id)) - return ON_SUBD_RETURN_ERROR(false); - unsigned char flags = 0; - if (!archive.ReadChar(&flags)) - return ON_SUBD_RETURN_ERROR(false); - element_ptr = archive_id; - element_ptr *= (ON_SUBD_COMPONENT_FLAGS_MASK+1); - element_ptr += (flags & ON_SUBD_COMPONENT_FLAGS_MASK); - return true; -} - -static bool WriteSavedLimitPointList( +static bool Internal_WriteSavedLimitPointList( unsigned int vertex_face_count, bool bHaveLimitPoint, const ON_SubDSectorSurfacePoint& limit_point, @@ -392,7 +435,7 @@ static bool WriteSavedLimitPointList( break; if (!Internal_WriteDouble3(limit_point.m_limitN, archive)) break; - if (!WriteArchiveIdAndFlags(limit_point.m_sector_face ? limit_point.m_sector_face->ArchiveId() : 0, 0, archive)) + if (!Internal_WriteArchiveIdAndFlags(limit_point.m_sector_face ? limit_point.m_sector_face->ArchiveId() : 0, 0, archive)) break; } return true; @@ -400,7 +443,7 @@ static bool WriteSavedLimitPointList( return ON_SUBD_RETURN_ERROR(false); } -static bool ReadSavedLimitPointList( +static bool Internal_ReadSavedLimitPointList( ON_BinaryArchive& archive, unsigned int vertex_face_count, ON_SimpleArray< ON_SubDSectorSurfacePoint > limit_points @@ -441,7 +484,7 @@ static bool ReadSavedLimitPointList( if (!Internal_ReadDouble3(archive,limit_point.m_limitN)) break; ON_SubDFacePtr fptr = ON_SubDFacePtr::Null; - if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr.m_ptr)) + if (!Internal_ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr.m_ptr)) break; limit_points.Append(limit_point); } @@ -457,7 +500,7 @@ static bool ReadSavedLimitPointList( return ON_SUBD_RETURN_ERROR(false); } -static bool WriteVertexList( +static bool Internal_WriteVertexList( unsigned short vertex_count, const ON_SubDVertex*const* vertex, ON_BinaryArchive& archive @@ -478,7 +521,7 @@ static bool WriteVertexList( for (i = 0; i < vertex_count; i++) { const ON_SubDVertex* v = vertex[i]; - if (!WriteArchiveIdAndFlags((nullptr != v) ? v->ArchiveId() : 0, ptr_flags, archive)) + if (!Internal_WriteArchiveIdAndFlags((nullptr != v) ? v->ArchiveId() : 0, ptr_flags, archive)) break; } if ( i < vertex_count ) @@ -490,7 +533,7 @@ static bool WriteVertexList( } -static bool ReadVertexList( +static bool Internal_ReadVertexList( ON_BinaryArchive& archive, unsigned short& vertex_count, unsigned short vertex_capacity, @@ -516,7 +559,7 @@ static bool ReadVertexList( for (i = 0; i < vertex_count; i++) { ON__UINT_PTR vptr = 0; - if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,vptr)) + if (!Internal_ReadArchiveIdAndFlagsIntoComponentPtr(archive,vptr)) break; vertex[i] = (ON_SubDVertex*)vptr; } @@ -529,7 +572,7 @@ static bool ReadVertexList( } -static bool WriteEdgePtrList( +static bool Internal_WriteEdgePtrList( unsigned short edge_count, unsigned short edgeN_capacity, const ON_SubDEdgePtr* edgeN, @@ -555,7 +598,7 @@ static bool WriteEdgePtrList( if ( i == edgeN_capacity) eptr = edgeX; const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr); - if (!WriteArchiveIdAndFlags((nullptr != edge) ? edge->ArchiveId() : 0,eptr->m_ptr,archive)) + if (!Internal_WriteArchiveIdAndFlags((nullptr != edge) ? edge->ArchiveId() : 0,eptr->m_ptr,archive)) break; } if ( i < edge_count ) @@ -567,7 +610,7 @@ static bool WriteEdgePtrList( } -static bool ReadEdgePtrList( +static bool Internal_ReadEdgePtrList( ON_BinaryArchive& archive, unsigned short& edge_count, unsigned short edgeN_capacity, @@ -598,7 +641,7 @@ static bool ReadEdgePtrList( { if ( i == edgeN_capacity) eptr = edgeX; - if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,eptr->m_ptr)) + if (!Internal_ReadArchiveIdAndFlagsIntoComponentPtr(archive,eptr->m_ptr)) break; } if ( i < edge_count ) @@ -610,7 +653,7 @@ static bool ReadEdgePtrList( } -static bool WriteFacePtrList( +static bool Internal_WriteFacePtrList( unsigned short face_count, size_t faceN_capacity, const ON_SubDFacePtr* faceN, @@ -636,7 +679,7 @@ static bool WriteFacePtrList( if ( i == faceN_capacity) fptr = faceX; const ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr->m_ptr); - if (!WriteArchiveIdAndFlags((nullptr != face) ? face->ArchiveId() : 0,fptr->m_ptr,archive)) + if (!Internal_WriteArchiveIdAndFlags((nullptr != face) ? face->ArchiveId() : 0,fptr->m_ptr,archive)) break; } if ( i < face_count ) @@ -648,7 +691,7 @@ static bool WriteFacePtrList( } -static bool ReadFacePtrList( +static bool Internal_ReadFacePtrList( ON_BinaryArchive& archive, unsigned short& face_count, unsigned short faceN_capacity, @@ -678,7 +721,7 @@ static bool ReadFacePtrList( { if ( i == faceN_capacity) fptr = faceX; - if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr->m_ptr)) + if (!Internal_ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr->m_ptr)) break; } if ( i < face_count ) @@ -689,6 +732,10 @@ static bool ReadFacePtrList( return ON_SUBD_RETURN_ERROR(false); } +ON_SubDComponentPtr& ON_SubDArchiveIdMap::SymmetrySetNextForExperts(const ON_SubDComponentBase& c) +{ + return const_cast(c.m_symmetry_set_next); +} bool ON_SubDVertex::Write( ON_BinaryArchive& archive @@ -700,21 +747,17 @@ bool ON_SubDVertex::Write( break; if (!archive.WriteChar((unsigned char)m_vertex_tag)) break; - //if (!archive.WriteChar((unsigned char)m_vertex_edge_order)) - // break; - //if (!archive.WriteChar((unsigned char)m_vertex_facet_type)) - // break; if (!Internal_WriteDouble3(m_P,archive)) break; if (!archive.WriteShort(m_edge_count)) break; if (!archive.WriteShort(m_face_count)) break; - if (!WriteSavedLimitPointList(m_face_count, this->SurfacePointIsSet(), m_limit_point, archive)) + if (!Internal_WriteSavedLimitPointList(m_face_count, this->SurfacePointIsSet(), m_limit_point, archive)) break; - if (!WriteEdgePtrList(m_edge_count,m_edge_capacity,m_edges,0,nullptr, archive)) + if (!Internal_WriteEdgePtrList(m_edge_count,m_edge_capacity,m_edges,0,nullptr, archive)) break; - if (!WriteFacePtrList(m_face_count,m_face_capacity,(const ON_SubDFacePtr*)m_faces,0,nullptr, archive)) + if (!Internal_WriteFacePtrList(m_face_count,m_face_capacity,(const ON_SubDFacePtr*)m_faces,0,nullptr, archive)) break; if (archive.Archive3dmVersion() < 70) @@ -759,10 +802,6 @@ bool ON_SubDVertex::Read( break; if (!archive.ReadChar(&vertex_tag)) break; - //if (!archive.ReadChar(&vertex_edge_order)) - // break; - //if (!archive.ReadChar(&vertex_facet_type)) - // break; if (!Internal_ReadDouble3(archive,P)) break; if (!archive.ReadShort(&edge_count)) @@ -770,7 +809,7 @@ bool ON_SubDVertex::Read( if (!archive.ReadShort(&face_count)) break; - if (!ReadSavedLimitPointList(archive, face_count, limit_points)) + if (!Internal_ReadSavedLimitPointList(archive, face_count, limit_points)) break; ON_SubDVertex* v = subdimple->AllocateVertex( @@ -787,14 +826,11 @@ bool ON_SubDVertex::Read( v->ON_SubDComponentBase::operator=(base); - //v->m_vertex_edge_order = ON_SubD::VertexEdgeOrderFromUnsigned(vertex_edge_order); - //v->m_vertex_facet_type = ON_SubD::VertexFacetTypeFromUnsigned(vertex_facet_type); - - if (!ReadEdgePtrList(archive,edge_count,v->m_edge_capacity,v->m_edges,0,nullptr)) + if (!Internal_ReadEdgePtrList(archive,edge_count,v->m_edge_capacity,v->m_edges,0,nullptr)) break; v->m_edge_count = edge_count; - if (!ReadFacePtrList(archive,face_count,v->m_face_capacity,(ON_SubDFacePtr*)v->m_faces,0,nullptr)) + if (!Internal_ReadFacePtrList(archive,face_count,v->m_face_capacity,(ON_SubDFacePtr*)v->m_faces,0,nullptr)) break; v->m_face_count = face_count; @@ -842,9 +878,9 @@ bool ON_SubDEdge::Write( break; if (!archive.WriteDouble(m_sharpness)) break; - if (!WriteVertexList(2, m_vertex, archive)) + if (!Internal_WriteVertexList(2, m_vertex, archive)) break; - if (!WriteFacePtrList(m_face_count,sizeof(m_face2)/sizeof(m_face2[0]),m_face2,m_facex_capacity,m_facex, archive)) + if (!Internal_WriteFacePtrList(m_face_count,sizeof(m_face2)/sizeof(m_face2[0]),m_face2,m_facex_capacity,m_facex, archive)) break; if (archive.Archive3dmVersion() < 70) @@ -893,7 +929,7 @@ bool ON_SubDEdge::Read( ON_SubDVertex* v[2] = { 0 }; unsigned short vertex_count = 2; - if (!ReadVertexList(archive, vertex_count, 2, v)) + if (!Internal_ReadVertexList(archive, vertex_count, 2, v)) break; ON_SubDEdge* e = subdimple->AllocateEdge( @@ -915,7 +951,7 @@ bool ON_SubDEdge::Read( e->m_sector_coefficient[1] = sector_weight[1]; e->m_sharpness = sharpness; - if (!ReadFacePtrList(archive,face_count,sizeof(e->m_face2)/sizeof(e->m_face2[0]),e->m_face2,e->m_facex_capacity,e->m_facex)) + if (!Internal_ReadFacePtrList(archive,face_count,sizeof(e->m_face2)/sizeof(e->m_face2[0]),e->m_face2,e->m_facex_capacity,e->m_facex)) break; e->m_face_count = face_count; @@ -952,7 +988,7 @@ bool ON_SubDFace::Write( if (!archive.WriteShort(m_edge_count)) break; - if (!WriteEdgePtrList(m_edge_count,sizeof(m_edge4)/sizeof(m_edge4[0]),m_edge4,m_edgex_capacity,m_edgex, archive)) + if (!Internal_WriteEdgePtrList(m_edge_count,sizeof(m_edge4)/sizeof(m_edge4[0]),m_edge4,m_edgex_capacity,m_edgex, archive)) break; if (archive.Archive3dmVersion() < 70) @@ -1101,7 +1137,7 @@ bool ON_SubDFace::Read( f->m_level_zero_face_id = level_zero_face_id; - if (!ReadEdgePtrList(archive, edge_count, sizeof(f->m_edge4) / sizeof(f->m_edge4[0]), f->m_edge4, f->m_edgex_capacity, f->m_edgex)) + if (!Internal_ReadEdgePtrList(archive, edge_count, sizeof(f->m_edge4) / sizeof(f->m_edge4[0]), f->m_edge4, f->m_edgex_capacity, f->m_edgex)) break; f->m_edge_count = edge_count; @@ -1685,12 +1721,10 @@ bool ON_SubDimple::Write( break; // minor version = 4 additions - // bSyncSymmetricContentSerialNumber = true when any SubD symmetry constraints are up to date. - const bool bSyncSymmetricContentSerialNumber - = m_symmetry.IsSet() - && gsn > 0 && gsn == m_symmetry.SymmetricObjectContentSerialNumber(); + // bSubDIsSymmetric = true if this subd currently has the symmetry specified by m_symmetry. + const bool bSubDIsSymmetric = m_symmetry.SameSymmetricObjectGeometry(this); - if (false == archive.WriteBool(bSyncSymmetricContentSerialNumber)) + if (false == archive.WriteBool(bSubDIsSymmetric)) break; if (false == archive.WriteUuid(m_face_packing_id)) @@ -1735,7 +1769,7 @@ bool ON_SubDimple::Read( unsigned int obsolete_archive_max_edge_id = 0; unsigned int obsolete_archive_max_face_id = 0; - bool bSyncSymmetricContentSerialNumber = false; + bool bSubDIsSymmetric = false; bool bSyncFacePackingHashSerialNumbers = false; for (;;) @@ -1793,23 +1827,16 @@ bool ON_SubDimple::Read( if (minor_version >= 3) { - ON__UINT64 gsn_at_save_time = 0; - if (false == archive.ReadBigInt(&gsn_at_save_time)) + // + ON__UINT64 legacy_gsn_at_save_time = 0; + if (false == archive.ReadBigInt(&legacy_gsn_at_save_time)) break; - if (3 == minor_version) - { - bSyncSymmetricContentSerialNumber - = gsn_at_save_time > 0 - && m_symmetry.IsSet() - && gsn_at_save_time == m_symmetry.SymmetricObjectContentSerialNumber(); - } if (minor_version >= 4) { // minor version = 4 additions - if (false == archive.ReadBool(&bSyncSymmetricContentSerialNumber)) + if (false == archive.ReadBool(&bSubDIsSymmetric)) break; - if (false == archive.ReadUuid(m_face_packing_id)) break; if (false == archive.ReadBool(&bSyncFacePackingHashSerialNumbers)) @@ -1881,10 +1908,23 @@ bool ON_SubDimple::Read( ChangeGeometryContentSerialNumber(false); - if (bSyncSymmetricContentSerialNumber) - m_symmetry.SetSymmetricObjectContentSerialNumber(GeometryContentSerialNumber()); + + /////////////////////////////////////////// + // + // No changes to "this SubD" below here. + // + // The rest is updating infomation that is used to determine if this SubD + // is the same SubD that existed when symmetry and texture information + // was saved. It's ok if this is not the same subd. If and when appropriate + // something downstream will update either the SubD or the symmetry/texture + // information. + // It most certainly is NOT appropriate to update any of that here. + // + + if (bSubDIsSymmetric) + m_symmetry.SetSymmetricObject(this); else - m_symmetry.ClearSymmetricObjectContentSerialNumber(); + m_symmetry.ClearSymmetricObject(); if (bSyncFacePackingHashSerialNumbers) { diff --git a/opennurbs_subd_copy.cpp b/opennurbs_subd_copy.cpp index a85b0bdf..446b029e 100644 --- a/opennurbs_subd_copy.cpp +++ b/opennurbs_subd_copy.cpp @@ -31,7 +31,7 @@ unsigned int ON_SubDArchiveIdMap::ArchiveIdFromComponentPtr(ON__UINT_PTR ptr) return (unsigned int)(ptr/(ON_SUBD_COMPONENT_FLAGS_MASK+1)); } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( const ON_SubDVertex* vertex ) { @@ -39,7 +39,7 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( return ON_SubDComponentPtr::Create((const ON_SubDVertex*)(archive_id*(ON_SUBD_COMPONENT_FLAGS_MASK+1))); } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( const ON_SubDEdge* edge ) { @@ -47,7 +47,7 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( return ON_SubDComponentPtr::Create((const ON_SubDEdge*)(archive_id*(ON_SUBD_COMPONENT_FLAGS_MASK+1))); } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( const ON_SubDFace* face ) { @@ -55,7 +55,7 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( return ON_SubDComponentPtr::Create((const ON_SubDFace*)(archive_id*(ON_SUBD_COMPONENT_FLAGS_MASK+1))); } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( ON_SubDVertexPtr vertex_ptr ) { @@ -64,7 +64,7 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromVertex( return ptr; } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( ON_SubDEdgePtr edge_ptr ) { @@ -73,7 +73,7 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromEdge( return ptr; } -ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( ON_SubDFacePtr face_ptr ) { @@ -82,6 +82,34 @@ ON_SubDComponentPtr ON_SubDArchiveIdMap::FromFace( return ptr; } +const ON_SubDComponentPtr ON_SubDArchiveIdMap::FromSymmetrySetNext( + ON_SubDComponentPtr::Type component_type, + const ON_SubDComponentBase* component +) +{ + if (nullptr == component) + return ON_SubDComponentPtr::Null; + + ON_SubDComponentPtr cptr; + switch (component_type) + { + case ON_SubDComponentPtr::Type::Vertex: + cptr = ON_SubDArchiveIdMap::FromVertex(component->m_symmetry_set_next.Vertex()); + break; + case ON_SubDComponentPtr::Type::Edge: + cptr = ON_SubDArchiveIdMap::FromEdge(component->m_symmetry_set_next.Edge()); + break; + case ON_SubDComponentPtr::Type::Face: + cptr = ON_SubDArchiveIdMap::FromFace(component->m_symmetry_set_next.Face()); + break; + case ON_SubDComponentPtr::Type::Unset: + default: + return ON_SubDComponentPtr::Null; + break; + } + return (1 == component->m_symmetry_set_next.ComponentDirection() && component->m_symmetry_set_next.IsNotNull()) ? cptr.SetComponentDirection() : cptr; +} + ON_SubDVertex* ON_SubDArchiveIdMap::CopyVertex( const ON_SubDVertex* source_vertex, class ON_SubDimple& subdimple @@ -103,7 +131,8 @@ ON_SubDVertex* ON_SubDArchiveIdMap::CopyVertex( const bool bCopyEdgeArray = true; const bool bCopyFaceArray = true; const bool bCopyLimitPointList = true; - vertex->CopyFrom(source_vertex,bCopyEdgeArray,bCopyFaceArray,bCopyLimitPointList); + const bool bCopySymmetrySetNext = true; + vertex->CopyFrom(source_vertex,bCopyEdgeArray,bCopyFaceArray,bCopyLimitPointList, bCopySymmetrySetNext); // convert vertex->m_edges[] pointers to archive_id values ON_SubDComponentPtr ptr; @@ -126,6 +155,9 @@ ON_SubDVertex* ON_SubDArchiveIdMap::CopyVertex( ptr = ON_SubDArchiveIdMap::FromFace(p->m_sector_face); const_cast(p)->m_sector_face = (const ON_SubDFace*)ptr.m_ptr; } + + // convert symmetry set next pointers to archive_id values + vertex->m_symmetry_set_next = ON_SubDArchiveIdMap::FromSymmetrySetNext(ON_SubDComponentPtr::Type::Vertex, vertex); return vertex; } @@ -148,7 +180,8 @@ ON_SubDEdge* ON_SubDArchiveIdMap::CopyEdge( const bool bReverseEdge = false; const bool bCopyVertexArray = true; const bool bCopyEdgeArray = true; - edge->CopyFrom(source_edge,bReverseEdge,bCopyVertexArray,bCopyEdgeArray); + const bool bCopySymmetryLoop = true; + edge->CopyFrom(source_edge,bReverseEdge,bCopyVertexArray,bCopyEdgeArray, bCopySymmetryLoop); // convert edge->m_vertex[] pointers to archive_id values ON_SubDComponentPtr ptr; @@ -167,6 +200,9 @@ ON_SubDEdge* ON_SubDArchiveIdMap::CopyEdge( fptr->m_ptr = ON_SubDArchiveIdMap::FromFace(*fptr).m_ptr; } + // convert symmetry set next pointers to archive_id values + edge->m_symmetry_set_next = ON_SubDArchiveIdMap::FromSymmetrySetNext(ON_SubDComponentPtr::Type::Edge, edge); + return edge; } @@ -182,7 +218,8 @@ ON_SubDFace* ON_SubDArchiveIdMap::CopyFace( return ON_SUBD_RETURN_ERROR(nullptr); const bool bCopyEdgeArray = true; - face->CopyFrom(source_face,bCopyEdgeArray); + const bool bCopySymmetryLoop = true; + face->CopyFrom(source_face,bCopyEdgeArray, bCopySymmetryLoop); // convert face->m_edges[] pointers to archive_id values ON_SubDEdgePtr* eptr = face->m_edge4; @@ -193,6 +230,9 @@ ON_SubDFace* ON_SubDArchiveIdMap::CopyFace( eptr->m_ptr = ON_SubDArchiveIdMap::FromEdge(*eptr).m_ptr; } + // convert symmetry set next pointers to archive_id values + face->m_symmetry_set_next = ON_SubDArchiveIdMap::FromSymmetrySetNext(ON_SubDComponentPtr::Type::Face, face); + return face; } @@ -343,6 +383,51 @@ bool ON_SubDArchiveIdMap::ConvertArchiveIdToRuntimeFacePtr( } return true; } + +bool ON_SubDArchiveIdMap::ConvertArchiveIdToRuntimeSymmetrySetNextPtr( + ON_SubDComponentPtr::Type component_type, + ON_SubDComponentBase* component +) +{ + if (nullptr == component) + return false; + const ON_SubDComponentPtr symmetry_set_next = component->m_symmetry_set_next; // symmetry_set_next stores archive id + component->m_symmetry_set_next = ON_SubDComponentPtr::Null; + if (symmetry_set_next.IsNull()) + return true; + if (component_type != symmetry_set_next.ComponentType()) + return ON_SUBD_RETURN_ERROR(false); + const unsigned archive_id = ON_SubDArchiveIdMap::ArchiveIdFromComponentPtr(symmetry_set_next.m_ptr); + if (archive_id < 1) + return ON_SUBD_RETURN_ERROR(false); + unsigned parition0 = 0; + switch (component_type) + { + case ON_SubDComponentPtr::Type::Unset: + return ON_SUBD_RETURN_ERROR(false); + break; + case ON_SubDComponentPtr::Type::Vertex: + parition0 = 0; + break; + case ON_SubDComponentPtr::Type::Edge: + parition0 = 1; + break; + case ON_SubDComponentPtr::Type::Face: + parition0 = 2; + break; + default: + return ON_SUBD_RETURN_ERROR(false); + break; + } + if(archive_id < m_archive_id_partition[parition0] || archive_id >= m_archive_id_partition[parition0+1]) + return ON_SUBD_RETURN_ERROR(false); + const ON_SubDComponentPtr* eleptr = ComponentPtrFromArchiveId(archive_id); + if ( nullptr == eleptr || eleptr->ComponentType() != component_type) + return ON_SUBD_RETURN_ERROR(false); + component->m_symmetry_set_next = (1 == symmetry_set_next.ComponentDirection()) ? eleptr->SetComponentDirection() : eleptr->ClearComponentDirection(); + return true; +} + void ON_SubDArchiveIdMap::ValidateArrayCounts( unsigned short& array_count, size_t arrayN_capacity, @@ -546,6 +631,9 @@ unsigned int ON_SubDArchiveIdMap::ConvertArchiveIdsToRuntimePointers() if ( 0 != p->m_sector_face ) ConvertArchiveIdToRuntimeFacePtr(1,1,(ON_SubDFacePtr*)&p->m_sector_face,0,nullptr); } + + // convert v->m_symmetry_set_next to runtime pointer. + ConvertArchiveIdToRuntimeSymmetrySetNextPtr(ON_SubDComponentPtr::Type::Vertex, v); } if ( archive_id != m_archive_id_partition[1] ) return ON_SUBD_RETURN_ERROR(0); @@ -561,6 +649,9 @@ unsigned int ON_SubDArchiveIdMap::ConvertArchiveIdsToRuntimePointers() ConvertArchiveIdToRuntimeVertexPtr(2,2,(ON_SubDVertex**)(e->m_vertex)); // convert ON_SubDEdge.m_face2[] and ON_SubDEdge.m_facex[] ConvertArchiveIdToRuntimeFacePtr(e->m_face_count,sizeof(e->m_face2)/sizeof(e->m_face2[0]),e->m_face2,e->m_facex_capacity,e->m_facex); + + // convert e->m_symmetry_set_next to runtime pointer. + ConvertArchiveIdToRuntimeSymmetrySetNextPtr(ON_SubDComponentPtr::Type::Edge, e); } if ( archive_id != m_archive_id_partition[2] ) return ON_SUBD_RETURN_ERROR(0); @@ -574,6 +665,9 @@ unsigned int ON_SubDArchiveIdMap::ConvertArchiveIdsToRuntimePointers() break; // convert ON_SubDFace.m_edge4[] and ON_SubDFace.m_edgex[] ON_SubDArchiveIdMap::ConvertArchiveIdToRuntimeEdgePtr(f->m_edge_count,sizeof(f->m_edge4)/sizeof(f->m_edge4[0]),f->m_edge4,f->m_edgex_capacity,f->m_edgex); + + // convert f->m_symmetry_set_next to runtime pointer. + ConvertArchiveIdToRuntimeSymmetrySetNextPtr(ON_SubDComponentPtr::Type::Face, f); } if ( archive_id != m_archive_id_partition[3] ) return ON_SUBD_RETURN_ERROR(0); @@ -664,19 +758,19 @@ ON__UINT64 ON_SubDimple::ChangeGeometryContentSerialNumber( bool bChangePreservesSymmetry ) const { - const bool bUpdateSymmetricObjectContentSerialNumber - = bChangePreservesSymmetry - && m_subd_geometry_content_serial_number > 0 - && m_symmetry.IsSet() - && m_subd_geometry_content_serial_number == m_symmetry.SymmetricObjectContentSerialNumber() - ; - + // For efficiency, must calculate bUpdateSymmetricObjectGeometry before changing m_subd_geometry_content_serial_number + const bool bUpdateSymmetricObjectGeometry = bChangePreservesSymmetry && m_symmetry.SameSymmetricObjectGeometry(this); + m_subd_geometry_content_serial_number = ON_NextContentSerialNumber(); m_subd_render_content_serial_number = m_subd_geometry_content_serial_number; // changing content automatically changes render content. - if (bUpdateSymmetricObjectContentSerialNumber) - m_symmetry.SetSymmetricObjectContentSerialNumber(m_subd_geometry_content_serial_number); - else - m_symmetry.ClearSymmetricObjectContentSerialNumber(); + + if (m_symmetry.IsSet()) + { + if (bUpdateSymmetricObjectGeometry) + m_symmetry.SetSymmetricObject(this); + else if (false == m_symmetry.SameSymmetricObjectTopology(this)) + m_symmetry.ClearSymmetricObject(); + } return m_subd_geometry_content_serial_number; } @@ -787,10 +881,24 @@ ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) m_symmetry = src.m_symmetry; - if (m_subd_geometry_content_serial_number != src.m_symmetry.SymmetricObjectContentSerialNumber()) + if ( + src.RuntimeSerialNumber == src.m_subd_toplology_hash.SubDRuntimeSerialNumber() + && m_subd_geometry_content_serial_number == src.m_subd_toplology_hash.SubDGeometryContentSerialNumber() + ) { - // symmetric content was out of date on src - m_symmetry.ClearSymmetricObjectContentSerialNumber(); + // src.m_subd_toplology_hash is valid - copy it to this + m_subd_toplology_hash = src.m_subd_toplology_hash; + m_subd_toplology_hash.m_subd_runtime_serial_number = this->RuntimeSerialNumber; + } + + if ( + src.RuntimeSerialNumber == src.m_subd_toplology_and_edge_creases_hash.SubDRuntimeSerialNumber() + && m_subd_geometry_content_serial_number == src.m_subd_toplology_and_edge_creases_hash.SubDGeometryContentSerialNumber() + ) + { + // src.m_subd_toplology_and_edge_crease_hash is valid - copy it to this + m_subd_toplology_and_edge_creases_hash = src.m_subd_toplology_and_edge_creases_hash; + m_subd_toplology_and_edge_creases_hash.m_subd_runtime_serial_number = this->RuntimeSerialNumber; } if ( @@ -802,16 +910,6 @@ ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) m_subd_geometry_hash = src.m_subd_geometry_hash; m_subd_geometry_hash.m_subd_runtime_serial_number = this->RuntimeSerialNumber; } - - if ( - src.RuntimeSerialNumber == src.m_subd_toplology_hash.SubDRuntimeSerialNumber() - && m_subd_geometry_content_serial_number == src.m_subd_toplology_hash.SubDGeometryContentSerialNumber() - ) - { - // src.m_subd_toplology_hash is valid - copy it to this - m_subd_toplology_hash = src.m_subd_toplology_hash; - m_subd_toplology_hash.m_subd_runtime_serial_number = this->RuntimeSerialNumber; - } } bool ON_SubDLevel::IsEmpty() const @@ -981,14 +1079,14 @@ const ON_SubDComponentBase* ON_SubDLevelComponentIdIterator::InternalNext() bool ON_SubDLevel::CopyHelper( const class ON_SubDimple& src_subdimple, const ON_SubDLevel& src_level, - class ON_SubDArchiveIdMap& eptrlist, + class ON_SubDArchiveIdMap& archive_id_map, class ON_SubDimple& dest_subdimple, bool bCopyComponentStatus ) { bool rc = false; - eptrlist.Reset(); + archive_id_map.Reset(); m_surface_mesh.Clear(); m_control_net_mesh.Clear(); @@ -996,11 +1094,11 @@ bool ON_SubDLevel::CopyHelper( for (;;) { bool bLevelLinkedListIncreasingId[3] = {}; - if ( 0 == src_level.SetArchiveId(src_subdimple, eptrlist.m_archive_id_partition, bLevelLinkedListIncreasingId) ) + if ( 0 == src_level.SetArchiveId(src_subdimple, archive_id_map.m_archive_id_partition, bLevelLinkedListIncreasingId) ) break; unsigned int archive_id = 1; - if ( archive_id != eptrlist.m_archive_id_partition[0]) + if ( archive_id != archive_id_map.m_archive_id_partition[0]) break; // Have to use idit because subd editing (deleting and then adding) can leave the level's linked lists @@ -1014,7 +1112,7 @@ bool ON_SubDLevel::CopyHelper( { if (archive_id != source_vertex->ArchiveId()) break; - ON_SubDVertex* vertex = eptrlist.AddCopy(source_vertex,dest_subdimple); + ON_SubDVertex* vertex = archive_id_map.AddCopy(source_vertex,dest_subdimple); if (nullptr == vertex ) break; if (archive_id != vertex->ArchiveId()) @@ -1023,7 +1121,7 @@ bool ON_SubDLevel::CopyHelper( if ( bCopyComponentStatus ) vertex->m_status = source_vertex->m_status; } - if ( archive_id != eptrlist.m_archive_id_partition[1]) + if ( archive_id != archive_id_map.m_archive_id_partition[1]) break; // must iterate source edges in order of increasing id @@ -1032,7 +1130,7 @@ bool ON_SubDLevel::CopyHelper( { if (archive_id != source_edge->ArchiveId()) break; - ON_SubDEdge* edge = eptrlist.AddCopy(source_edge,dest_subdimple); + ON_SubDEdge* edge = archive_id_map.AddCopy(source_edge,dest_subdimple); if (nullptr == edge ) break; if (archive_id != edge->ArchiveId()) @@ -1041,7 +1139,7 @@ bool ON_SubDLevel::CopyHelper( if ( bCopyComponentStatus ) edge->m_status = source_edge->m_status; } - if ( archive_id != eptrlist.m_archive_id_partition[2]) + if ( archive_id != archive_id_map.m_archive_id_partition[2]) break; // must iterate source faces in order of increasing id @@ -1050,7 +1148,7 @@ bool ON_SubDLevel::CopyHelper( { if (archive_id != source_face->ArchiveId()) break; - ON_SubDFace* face = eptrlist.AddCopy(source_face,dest_subdimple); + ON_SubDFace* face = archive_id_map.AddCopy(source_face,dest_subdimple); if (nullptr == face ) break; if (archive_id != face->ArchiveId()) @@ -1059,10 +1157,10 @@ bool ON_SubDLevel::CopyHelper( if ( bCopyComponentStatus ) face->m_status = source_face->m_status; } - if ( archive_id != eptrlist.m_archive_id_partition[3]) + if ( archive_id != archive_id_map.m_archive_id_partition[3]) break; - if (0 == eptrlist.ConvertArchiveIdsToRuntimePointers()) + if (0 == archive_id_map.ConvertArchiveIdsToRuntimePointers()) break; for (int meshdex = 0; meshdex < 2; meshdex++) @@ -1083,7 +1181,7 @@ bool ON_SubDLevel::CopyHelper( const_cast(fragment)->m_face = nullptr; if (0 != archive_id) { - if (eptrlist.ConvertArchiveIdToRuntimeFacePtr(1, 1, &fptr, 0, nullptr)) + if (archive_id_map.ConvertArchiveIdToRuntimeFacePtr(1, 1, &fptr, 0, nullptr)) { const_cast(fragment)->m_face = fptr.Face(); if (nullptr != fragment->m_face) @@ -1109,11 +1207,11 @@ bool ON_SubDLevel::CopyHelper( break; } - eptrlist.Reset(); + archive_id_map.Reset(); src_level.ClearArchiveId(); if ( false == rc ) return ON_SUBD_RETURN_ERROR(false); return rc; -} +} // archive_id_map diff --git a/opennurbs_subd_data.cpp b/opennurbs_subd_data.cpp index 32506b71..efe4d0a9 100644 --- a/opennurbs_subd_data.cpp +++ b/opennurbs_subd_data.cpp @@ -43,15 +43,33 @@ ON_SubDimple::~ON_SubDimple() Destroy(); } +ON_SubDHeap& ON_SubDimple::Heap() +{ + return m_heap; +} + void ON_SubDimple::Clear() { m_subd_appearance = ON_SubD::DefaultSubDAppearance; m_texture_coordinate_type = ON_SubDTextureCoordinateType::Unset; m_texture_mapping_tag = ON_MappingTag::Unset; + m_fragment_colors_mapping_tag = ON_MappingTag::Unset; + m_fragment_texture_settings_hash = ON_SHA1_Hash::EmptyContentHash; + m_fragment_colors_settings_hash = ON_SHA1_Hash::EmptyContentHash; for (unsigned i = 0; i < m_levels.UnsignedCount(); ++i) - delete m_levels[i]; + { + ON_SubDLevel* level = m_levels[i]; + if (nullptr != level) + { + m_levels[i] = nullptr; + delete level; + } + } + m_levels.SetCount(0); m_active_level = nullptr; m_heap.Clear(); + m_face_packing_id = ON_nil_uuid; + m_face_packing_topology_hash = ON_SubDHash::Empty; m_symmetry = ON_Symmetry::Unset; } @@ -227,7 +245,8 @@ void ON_SubDimple::Destroy() m_subd_render_content_serial_number = 0; } -ON_SubDLevel* ON_SubDimple::ActiveLevel(bool bCreateIfNeeded) +ON_SubDLevel* ON_SubDimple::ActiveLevel( + bool bCreateIfNeeded) { if (nullptr == m_active_level) { @@ -816,7 +835,16 @@ bool ON_SubDimple::Transform( const ON_Xform& xform ) { - const ON__UINT64 geometry_content_serial_number0 = GeometryContentSerialNumber(); + const bool bSymmetricInput = m_symmetry.SameSymmetricObjectGeometry(this); + const ON_Symmetry symmetry0 = m_symmetry; + const ON__UINT64 gsn0 = this->GeometryContentSerialNumber(); + + const bool bUpdateFacePackingHash + = m_face_packing_topology_hash.m_subd_geometry_content_serial_number == gsn0 + && m_face_packing_topology_hash.IsNotEmpty() + && m_face_packing_topology_hash.SubDHash() == this->SubDHash(ON_SubDHashType::TopologyAndEdgeCreases, false).SubDHash() + ; + if (false == xform.IsValid()) return false; @@ -857,34 +885,59 @@ bool ON_SubDimple::Transform( rc = false; break; } - } + // SubD has been moved - geometry changed and we need to bump the geometry content serial number. + this->ChangeGeometryContentSerialNumber(false); + + // GeometryContentSerial number trackers need to be updated + // so the SubD knows its status with respect to the + // newly transformed geometry. + + if (bUpdateFacePackingHash) + m_face_packing_topology_hash = this->SubDHash(ON_SubDHashType::TopologyAndEdgeCreases, false); if (m_symmetry.IsSet()) { - const ON_Symmetry symmetry0 = m_symmetry; m_symmetry = m_symmetry.TransformConditionally(xform); - bool bSetContentSerialNumber = false; - if (geometry_content_serial_number0 == symmetry0.SymmetricObjectContentSerialNumber()) + bool bSymmetricOutput = false; + if (bSymmetricInput) { // see if the transformed object will still be symmetric. if (ON_Symmetry::Coordinates::Object == m_symmetry.SymmetryCoordinates()) { // object is still symmetric. - bSetContentSerialNumber = true; + bSymmetricOutput = true; } else if (ON_Symmetry::Coordinates::World == m_symmetry.SymmetryCoordinates()) { - // if transform didn't move the symmetric + // if transform didn't move the symmetry if ( 0 == ON_Symmetry::CompareSymmetryTransformation(&symmetry0, &m_symmetry, ON_UNSET_VALUE) ) - bSetContentSerialNumber = true; + bSymmetricOutput = true; + } + } + if (bSymmetricOutput) + { + if (ON_Symmetry::Coordinates::Object == m_symmetry.SymmetryCoordinates()) + { + // symmetry constraints transformed with object + m_symmetry.SetSymmetricObject(this); + } + else + { + // object moved with respect to symmetry contstraints + // DO NOTHING HERE - the serial number and hashes on m_symmetry will inform downstream processes + // that the object no longer has the symmetry property specified by m_symmetry. + // It will get updated when appropriate - typically in replace object. + // EXAMPLE: Make a SubD plane - reflect it across the world Y axis. + // Then rotate the plane a bit. The rotated plane gets fixed in replace object. } } - if (bSetContentSerialNumber) - m_symmetry.SetSymmetricObjectContentSerialNumber(GeometryContentSerialNumber()); else - m_symmetry.ClearSymmetricObjectContentSerialNumber(); + { + // input was already dirty - remove all object settings from m_symmetry. + m_symmetry.ClearSymmetricObject(); + } } else { @@ -892,7 +945,6 @@ bool ON_SubDimple::Transform( } return rc; - } bool ON_SubDMeshFragment::Transform( @@ -1021,3 +1073,40 @@ const ON_BoundingBox ON_SubDFace::ControlNetBoundingBox() const return bbox; } +bool ON_Symmetry::SameSymmetricObjectGeometry(const class ON_SubD* subd) const +{ + const ON_SubDimple* subdimple = (nullptr != subd) ? subd->SubDimple() : nullptr; + return SameSymmetricObjectGeometry(subdimple); +} + +bool ON_Symmetry::SameSymmetricObjectTopology(const class ON_SubD* subd) const +{ + const ON_SubDimple* subdimple = (nullptr != subd) ? subd->SubDimple() : nullptr; + return SameSymmetricObjectTopology(subdimple); +} + +bool ON_Symmetry::SameSymmetricObjectGeometry(const class ON_SubDimple* subdimple) const +{ + if (this->IsSet() && m_symmetric_object_content_serial_number != 0 && nullptr != subdimple) + { + const ON__UINT64 subd_gsn = subdimple->GeometryContentSerialNumber(); + if (m_symmetric_object_content_serial_number == subd_gsn) + return true; // speedy check worked + if (m_symmetric_object_geometry_hash.IsSet() && m_symmetric_object_geometry_hash == subdimple->SubDHash(ON_SubDHashType::Geometry, false).SubDHash()) + return true; + } + return false; +} + +bool ON_Symmetry::SameSymmetricObjectTopology(const class ON_SubDimple* subdimple) const +{ + if (this->IsSet() && m_symmetric_object_content_serial_number != 0 && nullptr != subdimple) + { + const ON__UINT64 subd_gsn = subdimple->GeometryContentSerialNumber(); + if (m_symmetric_object_content_serial_number == subd_gsn) + return true; // speedy check worked (same geometry in fact!) + if (m_symmetric_object_topology_hash.IsSet() && m_symmetric_object_topology_hash == subdimple->SubDHash(ON_SubDHashType::Topology, false).SubDHash()) + return true; + } + return false; +} diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index bd579939..351a9b0f 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -600,6 +600,7 @@ public: unsigned int DumpTopology( + const ON_SubD& parent_subd, const unsigned int validate_max_vertex_id, const unsigned int validate_max_edge_id, const unsigned int validate_max_face_id, @@ -609,6 +610,7 @@ public: ON_SubDVertexIdIterator& vidit, ON_SubDEdgeIdIterator& eidit, ON_SubDFaceIdIterator& fidit, + bool bIncludeSymmetrySet, ON_TextLog& text_log ) const; @@ -636,7 +638,7 @@ public: bool CopyHelper( const class ON_SubDimple& src_subdimple, const ON_SubDLevel& src_level, - class ON_SubDArchiveIdMap& eptrlist, + class ON_SubDArchiveIdMap& archive_id_map, class ON_SubDimple& dest_subdimple, bool bCopyComponentStatus ); @@ -1158,7 +1160,7 @@ public: void ClearEvaluationCache() const; - bool CopyEvaluationCacheForExperts(const ON_SubDLevel& src, class ON_SubDHeap& this_heap); + bool CopyEvaluationCacheForExperts(class ON_SubDHeap& this_heap, const ON_SubDLevel& src, const class ON_SubDHeap& src_heap); void ClearTopologicalAttributes() const { @@ -1506,7 +1508,6 @@ public: const class ON_SubDFace* face ); - /* Description: Discard all contents of this ON_SubDHeap. @@ -1524,13 +1525,153 @@ public: size_t SizeOf() const { size_t sz = sizeof(*this); - sz += m_fsp5.SizeofElement()*m_fsp5.TotalElementCount(); - sz += m_fsp9.SizeofElement()*m_fsp9.TotalElementCount(); - sz += m_fsp17.SizeofElement()*m_fsp17.TotalElementCount(); - // todo - include m_ws; + + sz += SizeOfAllPools(); + + // For vertices with multiple sectors and multiple limit points, + // additional limit points come from a global limit point pool. + // In any given subd, this is a small minority of vertices and + // this undercounting of memory is a tiny compared with the + // pool sizes counted above. + + // For vertices with more than 17 faces or more than 17 edges, + // the ON_SubDVertex.m_faces[] and ON_SubDVertex.m_edges[] arrays + // are individually allocated. This is not common and the undercount + // is tiny compared with the pool sizes counted above. + + // For non-manifold edges with more than 17 faces, + // the ON_SubDEdge.m_faces[] array is individually allocated. + // This is extremely uncommon and the undercount + // is tiny compared with the pool sizes counted above. + + // For faces with more than 17 edges, + // the ON_SubDFace.m_edges[] array is individually allocated. + // This is uncommon and the undercount + // is tiny compared with the pool sizes counted above. + + // Add this fudge factor typically more than accounts for + // the uncommon and minimal undercounts that are described above. + sz += 8 * (m_fspv.ActiveElementCount() + m_fspe.ActiveElementCount() + m_fspf.ActiveElementCount()); + return sz; } + size_t SizeOfAllPools() const + { + size_t sz = 0; + sz += m_fspv.SizeOfPool(); + sz += m_fspe.SizeOfPool(); + sz += m_fspf.SizeOfPool(); + sz += m_fsp5.SizeOfPool(); + sz += m_fsp9.SizeOfPool(); + sz += m_fsp17.SizeOfPool(); + sz += m_fsp_full_fragments.SizeOfPool(); + sz += m_fsp_part_fragments.SizeOfPool(); + sz += m_fsp_oddball_fragments.SizeOfPool(); + sz += m_fsp_limit_curves.SizeOfPool(); + return sz; + } + + size_t SizeOfUnusedPoolElements() const + { + size_t sz = 0; + sz += m_fspv.SizeOfUnusedElements(); + sz += m_fspe.SizeOfUnusedElements(); + sz += m_fspf.SizeOfUnusedElements(); + sz += m_fsp5.SizeOfUnusedElements(); + sz += m_fsp9.SizeOfUnusedElements(); + sz += m_fsp17.SizeOfUnusedElements(); + sz += m_fsp_full_fragments.SizeOfUnusedElements(); + sz += m_fsp_part_fragments.SizeOfUnusedElements(); + sz += m_fsp_oddball_fragments.SizeOfUnusedElements(); + sz += m_fsp_limit_curves.SizeOfUnusedElements(); + return sz; + } + + size_t SizeOfActivePoolElements() const + { + size_t sz = 0; + sz += m_fspv.SizeOfActiveElements(); + sz += m_fspe.SizeOfActiveElements(); + sz += m_fspf.SizeOfActiveElements(); + sz += m_fsp5.SizeOfActiveElements(); + sz += m_fsp9.SizeOfActiveElements(); + sz += m_fsp17.SizeOfActiveElements(); + sz += m_fsp_full_fragments.SizeOfActiveElements(); + sz += m_fsp_part_fragments.SizeOfActiveElements(); + sz += m_fsp_oddball_fragments.SizeOfActiveElements(); + sz += m_fsp_limit_curves.SizeOfActiveElements(); + return sz; + } + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Total operating system heap memory (in bytes) used by the mesh fragments pool. + Remarks: + SizeOfMeshFragmentPool() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfMeshFragmentsPool() const + { + return + m_fsp_full_fragments.SizeOfPool() + + m_fsp_part_fragments.SizeOfPool() + + m_fsp_oddball_fragments.SizeOfPool() + + m_fsp_limit_curves.SizeOfPool(); + } + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Operating system heap memory (in bytes) that are used by active mesh fragments. + Remarks: + SizeOfMeshFragmentPool() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfActiveMeshFragments() const + { + return SizeOfMeshFragmentsPool() - SizeOfUnusedMeshFragments(); + } + + /* + Description: + Tool for debugging mesh fragments memory use. + Returns: + Operating system heap memory (in bytes) that has been reserved for mesh fragments + but is not currently used by active mesh fragments. + Remarks: + SizeOfMeshFragmentPool() = SizeOfActiveMeshFragments() + SizeOfUnusedMeshFragments(). + */ + size_t SizeOfUnusedMeshFragments() const + { + size_t sz + = m_fsp_full_fragments.SizeOfUnusedElements() + + m_fsp_part_fragments.SizeOfUnusedElements() + + m_fsp_oddball_fragments.SizeOfUnusedElements() + + m_fsp_limit_curves.SizeOfUnusedElements(); + + // It has alwasy been the case that count0 = count1 = ON_SubDDisplayParameters::MaximumDensity + 1. + // But a wrong answer is better than crashing if somebody incorrectly modifies ON_SubDHeap + // in the far future. + const size_t count0 = sizeof(m_unused_fragments) / sizeof(m_unused_fragments[0]); + const size_t count1 = sizeof(ON_SubDHeap::g_sizeof_fragment) / sizeof(ON_SubDHeap::g_sizeof_fragment[0]); + for (size_t i = 0; i < count0 && i < count1; ++i) + { + const size_t sizeof_fragment = ON_SubDHeap::g_sizeof_fragment[i]; + // The memory for these fragments is managed by m_fsp_full_fragments or m_fsp_part_fragments. + for (const ON_FixedSizePoolElement* e = m_unused_fragments[i]; nullptr != e; e = e->m_next) + sz += sizeof_fragment; + } + + return sz; + } + + + bool InHeap(ON_SubDComponentPtr cptr) const; + + const ON_SubDComponentPtr InHeap(const class ON_SubDComponentBase* b) const; + private: class tagWSItem { @@ -1573,25 +1714,64 @@ private: ON_FixedSizePool m_fsp9; // element = capacity + array of 8 ON__UINT_PTRs ON_FixedSizePool m_fsp17; // element = capacity + array of 16 ON__UINT_PTRs - // This pool is used to manage memory for - // - // 16x16 ON_SubDMeshFragment - // 8x8 ON_SubDMeshFragment - // 4x4 ON_SubDMeshFragment - // 2x2 ON_SubDMeshFragment - // 1x1 ON_SubDMeshFragment - // ON_SubDEdgeSurfaceCurve - bool Internal_InitializeLimitBlockPool(); - ON_FixedSizePool m_limit_block_pool; - - // m_sizeof_fragment[i] = sizeof of a fragment NxN mesh quads where N = 2^i - // m_sizeof_fragment[0] = sizeof of a fragment with 1x1 mesh quads. - // m_sizeof_fragment[1] = sizeof of a fragment with 2x2 mesh quads. - // m_sizeof_fragment[2] = sizeof of a fragment with 4x4 mesh quads. - // m_sizeof_fragment[3] = sizeof of a fragment with 8x8 mesh quads. - // m_sizeof_fragment[4] = sizeof of a fragment with 16x16 mesh quads. + // g_sizeof_fragment[i] = sizeof of a fragment NxN mesh quads where N = 2^i + // g_sizeof_fragment[0] = sizeof of a fragment with 1x1 mesh quads. (quad fragment for density = 0) + // g_sizeof_fragment[1] = sizeof of a fragment with 2x2 mesh quads. (quad fragment for density = 1) + // g_sizeof_fragment[2] = sizeof of a fragment with 4x4 mesh quads. (quad fragment for density = 2) + // g_sizeof_fragment[3] = sizeof of a fragment with 8x8 mesh quads. (quad fragment for density = 3) + // g_sizeof_fragment[4] = sizeof of a fragment with 16x16 mesh quads. (quad fragment for density = 4) // ... - size_t m_sizeof_fragment[ON_SubDDisplayParameters::MaximumDensity + 1] = {}; + static const size_t g_sizeof_fragment[ON_SubDDisplayParameters::MaximumDensity + 1]; + + + /* + Sets m_full_fragment_display_density, m_full_fragment_count_estimate, m_part_fragment_count_estimate. + */ + bool Internal_InitializeFragmentCountEstimates( + unsigned subd_display_density + ); + + // m_full_fragment_display_density = display density for a full fragment + unsigned int m_full_fragment_display_density = 0; + + // m_full_fragment_count_estimate = an esitmate of the total number of full fragments + // needed. It's ok if we need additionaly fragments later. + unsigned int m_full_fragment_count_estimate = 0; + + // m_full_fragment_count_estimate = an esitmate of the total number of full fragments + // needed. It's ok if we need additionaly fragments later. + unsigned int m_part_fragment_count_estimate = 0; + + unsigned int m_reserved0 = 0; + + /* + Parameters: + fragment_count_estimate - [in] + Estimate of the number of fragments that will come from this pool. + sizeof_fragment - [in] + number of bytes per most common fragment (ON_SubDMeshFragment + vertex arrays) + max_sizeof_fragment - [in] + number of bytes per maximum possible sized fragment (ON_SubDMeshFragment + vertex arrays) + from fsp. + fsp - [in/out] + fixed size pool to initialize. + */ + static bool Internal_InitializeMeshFragmentPool( + size_t sizeof_element, + size_t element_count_estimate, + size_t min_2nd_block_element_count, + ON_FixedSizePool& fsp // fsp references either m_fsp_*_fragments or m_fsp_limit_curves. + ); + + // ON_SubDMeshFragments with density = m_full_fragment_display_density are allocated from m_fsp_full_fragments. + ON_FixedSizePool m_fsp_full_fragments; + + // ON_SubDMeshFragments with density+1 = m_full_fragment_display_density are allocated from m_fsp_part_fragments. + ON_FixedSizePool m_fsp_part_fragments; + + // ON_SubDMeshFragments with density < m_full_fragment_display_density-1 || density > m_full_fragment_display_density + // are allocated from m_fsp_oddball_fragments. In all common cases, m_fsp_oddball_fragments is not used. + ON_FixedSizePool m_fsp_oddball_fragments; // m_unused_fragments[0] = unused 1x1 mesh quad fragments. // m_unused_fragments[1] = unused 2x2 mesh quad fragments. @@ -1599,19 +1779,25 @@ private: // m_unused_fragments[3] = unused 8x8 mesh quad fragments. // m_unused_fragments[4] = unused 16x16 mesh quad fragments. // ... + // The memory for these fragments is managed by m_fragment_pool. class ON_FixedSizePoolElement* m_unused_fragments[ON_SubDDisplayParameters::MaximumDensity + 1] = {}; - // Used to allocate edge curves - size_t m_sizeof_limit_curve = 0; - class ON_FixedSizePoolElement* m_unused_limit_curves = nullptr; + bool Internal_InitializeLimitCurvesPool(); + + // Used to allocate edge curves (ON_SubDEdgeSurfaceCurve class that ON_SubDEdge.m_limit_curve points to + // The memory for these curves is managed by m_limit_block_pool. + ON_FixedSizePool m_fsp_limit_curves; // list of vertices previously allocated from m_fspv and returned by ReturnVertex(). + // The memory for these vertices is managed by m_fspv. ON_SubDVertex* m_unused_vertex = nullptr; // list of edges previously allocated from m_fspv and returned by ReturnVertex(). + // The memory for these edges is managed by m_fspe. ON_SubDEdge* m_unused_edge = nullptr; // list of faces previously allocated from m_fspv and returned by ReturnVertex(). + // The memory for these faces is managed by m_fspf. ON_SubDFace* m_unused_face = nullptr; // Maximum vertex id assigned to a vertex in m_fspv. @@ -1648,6 +1834,15 @@ private: class ON_3dPoint* point_array ); + +private: + ON_FixedSizePool* Internal_ComponentFixedSizePool( + ON_SubDComponentPtr::Type component_type + ); + const ON_FixedSizePool* Internal_ComponentFixedSizePool( + ON_SubDComponentPtr::Type component_type + ) const; + public: static unsigned int Managed3dPointArrayCapacity(class ON_3dPoint* point_array); @@ -1690,10 +1885,15 @@ public: const ON_SubDHash SubDHash( ON_SubDHashType hash_type, - const ON_SubD& parent_subd, bool bForceUpdate ) const; + const ON_SHA1_Hash VertexHash(ON_SubDHashType hash_type) const; + + const ON_SHA1_Hash EdgeHash(ON_SubDHashType hash_type) const; + + const ON_SHA1_Hash FaceHash(ON_SubDHashType hash_type) const; + /* Returns: A runtime serial number that is incremented every time a component status @@ -1751,8 +1951,10 @@ public: private: mutable ON__UINT64 m_subd_geometry_content_serial_number = 0; mutable ON__UINT64 m_subd_render_content_serial_number = 0; - mutable ON_SubDHash m_subd_geometry_hash = ON_SubDHash::Empty; + mutable ON_SubDHash m_subd_toplology_hash = ON_SubDHash::Empty; + mutable ON_SubDHash m_subd_toplology_and_edge_creases_hash = ON_SubDHash::Empty; + mutable ON_SubDHash m_subd_geometry_hash = ON_SubDHash::Empty; public: @@ -2131,6 +2333,36 @@ public: return sz; } + size_t SizeOfAllElements() const + { + return m_heap.SizeOfAllPools(); + } + + size_t SizeOfActiveElements() const + { + return m_heap.SizeOfActivePoolElements(); + } + + size_t SizeOfUnusedElements() const + { + return m_heap.SizeOfUnusedPoolElements(); + } + + size_t SizeOfAllMeshFragments() const + { + return m_heap.SizeOfMeshFragmentsPool(); + } + + size_t SizeOfActiveMeshFragments() const + { + return m_heap.SizeOfActiveMeshFragments(); + } + + size_t SizeOfUnusedMeshFragments() const + { + return m_heap.SizeOfUnusedMeshFragments(); + } + bool GlobalSubdivide( unsigned int count ); @@ -2184,6 +2416,9 @@ private: friend class ON_Internal_SubDFaceMeshFragmentAccumulator; ON_SubDHeap m_heap; +public: + ON_SubDHeap& Heap(); + public: void InitializeVertexIdIterator( class ON_SubDVertexIdIterator& vidit @@ -2369,7 +2604,7 @@ public: void SetFacePackingIdAndTopologyHash( ON_UUID custom_packing_id, - const ON_SubDHash& current_topology_hash + const ON_SubDHash& current_topology_and_creases_hash ); /* @@ -2378,7 +2613,7 @@ public: */ void ClearFacePackIds(); - void SetFacePackingTopologyHashForExperts(const ON_SubDHash& current_topology_hash) const; + void SetFacePackingTopologyHashForExperts(const ON_SubDHash& current_topology_and_creases_hash) const; private: ON_UUID m_face_packing_id = ON_nil_uuid; @@ -2522,6 +2757,13 @@ public: ON_SubDFacePtr* faceX ); +private: + bool ConvertArchiveIdToRuntimeSymmetrySetNextPtr( + ON_SubDComponentPtr::Type component_type, + ON_SubDComponentBase* component + ); + +public: static void ValidateArrayCounts( unsigned short& array_count, size_t arrayN_capacity, @@ -2530,30 +2772,38 @@ public: const void* arrayX ); - static ON_SubDComponentPtr FromVertex( +public: + static const ON_SubDComponentPtr FromVertex( ON_SubDVertexPtr vertex_ptr ); - static ON_SubDComponentPtr FromEdge( + static const ON_SubDComponentPtr FromEdge( ON_SubDEdgePtr edge_ptr ); - static ON_SubDComponentPtr FromFace( + static const ON_SubDComponentPtr FromFace( ON_SubDFacePtr face_ptr ); - static ON_SubDComponentPtr FromVertex( + static const ON_SubDComponentPtr FromVertex( const ON_SubDVertex* vertex ); - static ON_SubDComponentPtr FromEdge( + static const ON_SubDComponentPtr FromEdge( const ON_SubDEdge* edge ); - static ON_SubDComponentPtr FromFace( + static const ON_SubDComponentPtr FromFace( const ON_SubDFace* face ); +private: + static const ON_SubDComponentPtr FromSymmetrySetNext( + ON_SubDComponentPtr::Type component_type, + const ON_SubDComponentBase* component + ); + +public: static ON_SubDVertex* CopyVertex( const ON_SubDVertex* src, class ON_SubDimple& subdimple @@ -2594,6 +2844,9 @@ public: unsigned int ConvertArchiveIdsToRuntimePointers(); +public: + static ON_SubDComponentPtr& SymmetrySetNextForExperts(const ON_SubDComponentBase&); + private: bool AddComponentPtr(ON_SubDComponentPtr eptr, unsigned int archive_id); @@ -2788,7 +3041,7 @@ public: }; private: - // It is critical that sizeof(ON_SubDEdgeSurfaceCurve) = 6*3*sizeof(double). + // It is critical that sizeof(ON_SubDEdgeSurfaceCurve) >= (MaximumControlPointCapacity-MinimumControlPointCapacity)*3*sizeof(double). // The edge curve cache relies on this. // Do not remove m_reserved* fields. ON__UINT64 m_reserved0 = 0; // overlaps with ON_FixedSizePoolElement.m_next. diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index 01c58e84..0bb762de 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -1022,6 +1022,49 @@ void ON_SubDVertex::ClearSavedSubdivisionPoints() const ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } +void ON_SubDVertex::ClearSavedSubdivisionPoints(bool bClearNeighborhood) const +{ + ClearSavedSubdivisionPoints(); + if (false == bClearNeighborhood) + return; + + for (unsigned short vei = 0; vei < m_edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + if (nullptr == e) + continue; + e->ClearSavedSubdivisionPoints(); + const ON_SubDVertex* other_v = e->OtherEndVertex(this); + if (nullptr != other_v) + other_v->ClearSavedSubdivisionPoints(); + } + for (unsigned short vfi = 0; vfi < m_face_count; ++vfi) + { + const ON_SubDFace* f = m_faces[vfi]; + if (nullptr == f) + continue; + f->ClearSavedSubdivisionPoints(); + const ON_SubDEdgePtr* eptr = f->m_edge4; + for (unsigned short fei = 0; fei < f->m_edge_count; ++fei, ++eptr) + { + if (4 == fei) + { + eptr = f->m_edgex; + if (nullptr == eptr) + break; + } + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr->m_ptr); + if (nullptr == e) + continue; + e->ClearSavedSubdivisionPoints(); + if (nullptr != e->m_vertex[0]) + e->m_vertex[0]->ClearSavedSubdivisionPoints(); + if (nullptr != e->m_vertex[1]) + e->m_vertex[1]->ClearSavedSubdivisionPoints(); + } + } +} + bool ON_SubDVertex::SurfacePointIsSet() const { const bool rc = Internal_SurfacePointFlag(); @@ -1039,6 +1082,35 @@ void ON_SubDEdge::ClearSavedSubdivisionPoints() const ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } +void ON_SubDEdge::ClearSavedSubdivisionPoints(bool bClearNeighborhood) const +{ + ClearSavedSubdivisionPoints(); + if (false == bClearNeighborhood) + return; + + for (unsigned evi = 0; evi < 2; ++evi) + { + const ON_SubDVertex* v = m_vertex[evi]; + if (nullptr == v) + continue; + v->ClearSavedSubdivisionPoints(); + } + + const ON_SubDFacePtr* fptr = this->m_face2; + for (unsigned short efi = 0; efi < m_face_count; ++efi, ++fptr) + { + if (2 == efi) + { + fptr = this->m_facex; + if (nullptr == fptr) + break; + } + const ON_SubDFace* f = ON_SUBD_FACE_POINTER(fptr->m_ptr); + if (nullptr != f) + f->ClearSavedSubdivisionPoints(); + } +} + const ON_SubDMeshFragment * ON_SubDFace::MeshFragments() const { // NOTE: @@ -1054,6 +1126,35 @@ void ON_SubDFace::ClearSavedSubdivisionPoints() const ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } +void ON_SubDFace::ClearSavedSubdivisionPoints(bool bClearNeighborhood) const +{ + ClearSavedSubdivisionPoints(); + if (false == bClearNeighborhood) + return; + + const ON_SubDEdgePtr* eptr = this->m_edge4; + for (unsigned short fei = 0; fei < this->m_edge_count; ++fei, ++eptr) + { + if (4 == fei) + { + eptr = this->m_edgex; + if (nullptr == eptr) + break; + } + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr->m_ptr); + if (nullptr == e) + continue; + e->ClearSavedSubdivisionPoints(); + for (unsigned evi = 0; evi < 2; ++evi) + { + const ON_SubDVertex* v = e->m_vertex[evi]; + if (nullptr == v) + continue; + v->ClearSavedSubdivisionPoints(); + } + } +} + unsigned int ON_SubDVertex::VertexId() const { diff --git a/opennurbs_subd_heap.cpp b/opennurbs_subd_heap.cpp index 6ff754e6..594d1977 100644 --- a/opennurbs_subd_heap.cpp +++ b/opennurbs_subd_heap.cpp @@ -27,6 +27,26 @@ */ +enum +{ + // change bAllFragmentsHaveCurvature to true when principal and sectional curvatures are needed + // or delete this enum and always make room for curvatures. + ON_Internal_bAllFragmentsHaveCurvature = 0 +}; + + + +const size_t ON_SubDHeap::g_sizeof_fragment[ON_SubDDisplayParameters::MaximumDensity + 1] = +{ + ON_SubDMeshFragment::SizeofFragment(0, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(1, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(2, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(3, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(4, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(5, ON_Internal_bAllFragmentsHaveCurvature), + ON_SubDMeshFragment::SizeofFragment(ON_SubDDisplayParameters::MaximumDensity, ON_Internal_bAllFragmentsHaveCurvature) +}; + static void* ON_SubD__Allocate(size_t sz) { if (0 == sz) @@ -1032,6 +1052,9 @@ void ON_SubDHeap::ReturnVertex(class ON_SubDVertex* v) v->m_status = ON_ComponentStatus::Deleted; v->m_next_vertex = m_unused_vertex; m_unused_vertex = v; + // It is critical that v->m_symmetry_set_next remains set + // so deleted elements of symmetric SubDs can be found. + // NO! // m_fspv.ReturnElement(v); // See comments in AllocateVertexAndSetId(); } @@ -1062,6 +1085,9 @@ void ON_SubDHeap::ReturnEdge(class ON_SubDEdge* e) e->m_status = ON_ComponentStatus::Deleted; e->m_next_edge = m_unused_edge; m_unused_edge = e; + // It is critical that e->m_symmetry_set_next remains set + // so deleted elements of symmetric SubDs can be found. + // NO! // m_fspe.ReturnElement(e); // See comments in AllocateVertexAndSetId(); } @@ -1099,6 +1125,9 @@ void ON_SubDHeap::ReturnFace(class ON_SubDFace* f) f->m_status = ON_ComponentStatus::Deleted; f->m_next_face = m_unused_face; m_unused_face = f; + // It is critical that f->m_symmetry_set_next remains set + // so deleted elements of symmetric SubDs can be found. + // NO! // m_fspf.ReturnElement(f); // See comments in AllocateVertexAndSetId(); } @@ -1123,12 +1152,18 @@ void ON_SubDHeap::Clear() m_fsp9.ReturnAll(); m_fsp17.ReturnAll(); - m_limit_block_pool.ReturnAll(); + m_full_fragment_display_density = 0; + m_full_fragment_count_estimate = 0; + m_part_fragment_count_estimate = 0; + + m_fsp_full_fragments.ReturnAll(); + m_fsp_part_fragments.ReturnAll(); + m_fsp_oddball_fragments.ReturnAll(); + m_fsp_limit_curves.ReturnAll(); const size_t frag_size_count = sizeof(m_unused_fragments) / sizeof(m_unused_fragments[0]); - for ( size_t i = 0; i < frag_size_count; ++i) + for (size_t i = 0; i < frag_size_count; ++i) m_unused_fragments[i] = nullptr; - m_unused_limit_curves = nullptr; m_unused_vertex = nullptr; m_unused_edge = nullptr; @@ -1287,6 +1322,53 @@ void ON_SubDHeap::ResetIds() m_max_face_id = (next_face_id > first_id) ? (next_face_id - 1U) : 0U; } +ON_FixedSizePool* ON_SubDHeap::Internal_ComponentFixedSizePool( + ON_SubDComponentPtr::Type component_type +) +{ + switch (component_type) + { + case ON_SubDComponentPtr::Type::Unset: + break; + case ON_SubDComponentPtr::Type::Vertex: + return &m_fspv; + break; + case ON_SubDComponentPtr::Type::Edge: + return &m_fspe; + break; + case ON_SubDComponentPtr::Type::Face: + return &m_fspf; + break; + default: + break; + } + return nullptr; +} + +const ON_FixedSizePool* ON_SubDHeap::Internal_ComponentFixedSizePool( + ON_SubDComponentPtr::Type component_type +) const +{ + switch (component_type) + { + case ON_SubDComponentPtr::Type::Unset: + break; + case ON_SubDComponentPtr::Type::Vertex: + return &m_fspv; + break; + case ON_SubDComponentPtr::Type::Edge: + return &m_fspe; + break; + case ON_SubDComponentPtr::Type::Face: + return &m_fspf; + break; + default: + break; + } + return nullptr; +} + + size_t ON_SubDHeap::OversizedElementCapacity(size_t count) { size_t capacity = 32 * (count / 32); @@ -1793,26 +1875,94 @@ void ON_SubDHeap::ReturnArray( return; } -bool ON_SubDHeap::Internal_InitializeLimitBlockPool() +bool ON_SubDHeap::Internal_InitializeLimitCurvesPool() { - if (0 == m_limit_block_pool.SizeofElement()) + if (0 == m_fsp_limit_curves.SizeofElement()) { - const bool bCurvatureArray = false; // change to true when principal and sectional curvatures are needed. - const unsigned frag_size_count = sizeof(m_sizeof_fragment) / sizeof(m_sizeof_fragment[0]); - for ( unsigned i = 0; i < frag_size_count ; ++i) - m_sizeof_fragment[i] = ON_SubDMeshFragment::SizeofFragment((unsigned)i, bCurvatureArray); - m_sizeof_limit_curve = sizeof(ON_SubDEdgeSurfaceCurve); - size_t sz = 2*m_sizeof_fragment[frag_size_count-1]; - if (sz < 4 * m_sizeof_fragment[frag_size_count - 2]) - sz = 4 * m_sizeof_fragment[frag_size_count - 2]; + unsigned subd_edge_count = 0; + // count edge and face to get an estimate of haow many mesh fragments we need to managed. + ON_FixedSizePoolIterator fspeit(m_fspe); + for (const void* p = fspeit.FirstElement(); nullptr != p; p = fspeit.NextElement()) + { + const ON_SubDEdge* e = (const ON_SubDEdge*)p; + if (false == e->IsActive()) + continue; + ++subd_edge_count; + } - ON_SleepLockGuard guard(m_limit_block_pool); - m_limit_block_pool.Create(sz,0,0); - // check size again in case another thread beat this call - if (0 == m_limit_block_pool.SizeofElement()) - m_limit_block_pool.Create(sz, 0, 0); + const size_t sizeof_element = sizeof(ON_SubDEdgeSurfaceCurve); + + Internal_InitializeMeshFragmentPool( + sizeof(ON_SubDEdgeSurfaceCurve), + subd_edge_count, + 32, + m_fsp_limit_curves + ); } - return (m_limit_block_pool.SizeofElement() > 0); + return (m_fsp_limit_curves.SizeofElement() > 0); +} + +bool ON_SubDHeap::Internal_InitializeFragmentCountEstimates( + unsigned subd_display_density +) +{ + if (0 == m_full_fragment_display_density) + { + m_full_fragment_display_density + = subd_display_density > 0 + ? (subd_display_density <= ON_SubDDisplayParameters::MaximumDensity ? subd_display_density : ON_SubDDisplayParameters::MaximumDensity) + : 1U + ; + + // Count the number of active faces and fragments needed to mesh them + unsigned subd_face_count = 0; + unsigned full_frag_count = 0; + unsigned part_frag_count = 0; + + ON_FixedSizePoolIterator fspfit(m_fspf); + for (const void* p = fspfit.FirstElement(); nullptr != p; p = fspfit.NextElement()) + { + const ON_SubDFace* f = (const ON_SubDFace*)p; + if (false == f->IsActive()) + continue; + ++subd_face_count; + if (4 == f->m_edge_count) + ++full_frag_count; // one full fragment per quad + else + part_frag_count += f->m_edge_count; // n partial fragments per n-gon when n != 4 + } + + if (full_frag_count > 0 || part_frag_count > 0) + { + m_full_fragment_count_estimate = full_frag_count; + m_part_fragment_count_estimate = part_frag_count; + } + else + { + m_full_fragment_count_estimate = 0; + m_part_fragment_count_estimate = 0; + } + } + + return (m_full_fragment_display_density > 0 && m_full_fragment_display_density <= ON_SubDDisplayParameters::MaximumDensity); +} + +bool ON_SubDHeap::Internal_InitializeMeshFragmentPool( + size_t sizeof_element, + size_t element_count_estimate, + size_t min_fsp_2nd_block_element_count, + ON_FixedSizePool& fsp // fsp references either m_fsp_*_fragments or m_fsp_limit_curves. +) +{ + if (0 == fsp.SizeofElement() && sizeof_element > 0) + { + ON_SleepLockGuard guard(fsp); + fsp.CreateForExperts(sizeof_element, element_count_estimate, min_fsp_2nd_block_element_count); + // check size again in case another thread beat this call + if (0 == fsp.SizeofElement()) + fsp.CreateForExperts(sizeof_element, element_count_estimate, min_fsp_2nd_block_element_count); + } + return (fsp.SizeofElement() > 0); } ON_SubDMeshFragment* ON_SubDHeap::AllocateMeshFragment( @@ -1826,6 +1976,8 @@ ON_SubDMeshFragment* ON_SubDHeap::AllocateMeshFragment( // When 4 == ON_SubDDisplayParameters::DefaultDensity (setting used in February 2019) // quads get a single fragment with a 16x16 face grid // N-gons with N != 4 get N 8x8 grids. + + // density = density of src_fragment const unsigned int density = (src_fragment.m_face_fragment_count > 1) ? ((subd_display_density > 0) ? (subd_display_density -1) : ON_UNSET_UINT_INDEX) : ((1==src_fragment.m_face_fragment_count) ? subd_display_density : ON_UNSET_UINT_INDEX) @@ -1841,23 +1993,69 @@ ON_SubDMeshFragment* ON_SubDHeap::AllocateMeshFragment( if ( src_fragment.VertexCount() > 0 && src_fragment.VertexCount() < ((unsigned)vertex_capacity) ) return ON_SUBD_RETURN_ERROR(nullptr); - if (0 == m_limit_block_pool.SizeofElement()) - Internal_InitializeLimitBlockPool(); + if (0 == m_full_fragment_display_density) + { + // Lazy initialization of m_full_fragment_display_density is done because + // we don't know the display density when SubDs are being constructed. + if (false == Internal_InitializeFragmentCountEstimates(subd_display_density)) + return ON_SUBD_RETURN_ERROR(nullptr); + } + if (m_full_fragment_display_density <= 0 || m_full_fragment_display_density >= ON_SubDDisplayParameters::MaximumDensity) + return ON_SUBD_RETURN_ERROR(nullptr); + + // In all common situations, bUseFullFragmentFSP or bUsePartFragmentFSP is true. + const bool bUseFullFragmentFSP = (density == this->m_full_fragment_display_density); + const bool bUsePartFragmentFSP = (density+1 == this->m_full_fragment_display_density); + + ON_FixedSizePool& fsp + = bUseFullFragmentFSP + ? m_fsp_full_fragments + : bUsePartFragmentFSP ? m_fsp_part_fragments : m_fsp_oddball_fragments; + + if (0 == fsp.SizeofElement()) + { + // Lazy initialization of the fragment fixed size pools + // is done so that we don't reserve pool memory that never gets used. + + const size_t sizeof_fragment + = bUseFullFragmentFSP + ? ON_SubDHeap::g_sizeof_fragment[m_full_fragment_display_density] + : (bUsePartFragmentFSP ? ON_SubDHeap::g_sizeof_fragment[m_full_fragment_display_density - 1U] : ON_SubDHeap::g_sizeof_fragment[ON_SubDDisplayParameters::MaximumDensity]) + ; + + const size_t fragment_count_estimate + = bUseFullFragmentFSP + ? m_full_fragment_count_estimate + : (bUsePartFragmentFSP ? m_part_fragment_count_estimate : ((unsigned)4U)) + ; + + const size_t min_fsp_2nd_block_element_count = (bUseFullFragmentFSP || bUsePartFragmentFSP) ? 32 : 1; + + if (false == this->Internal_InitializeMeshFragmentPool( + sizeof_fragment, + fragment_count_estimate, + min_fsp_2nd_block_element_count, + fsp + )) + { + return ON_SUBD_RETURN_ERROR(nullptr); + } + } ON_SubDMeshFragment* fragment; { char* p = nullptr; char* p1 = nullptr; - ON_SleepLockGuard guard(m_limit_block_pool); + ON_SleepLockGuard guard(fsp); if (nullptr == m_unused_fragments[density]) { - p = (char*)m_limit_block_pool.AllocateDirtyElement(); + p = (char*)fsp.AllocateDirtyElement(); if (nullptr == p) return ON_SUBD_RETURN_ERROR(nullptr); - p1 = p + m_limit_block_pool.SizeofElement(); + p1 = p + fsp.SizeofElement(); m_unused_fragments[density] = (ON_FixedSizePoolElement*)p; m_unused_fragments[density]->m_next = nullptr; - const size_t sizeof_fragment = m_sizeof_fragment[density]; + const size_t sizeof_fragment = ON_SubDHeap::g_sizeof_fragment[density]; for (p += sizeof_fragment; p + sizeof_fragment <= p1; p += sizeof_fragment) { ON_FixedSizePoolElement* ele = (ON_FixedSizePoolElement*)p; @@ -1867,16 +2065,6 @@ ON_SubDMeshFragment* ON_SubDHeap::AllocateMeshFragment( } fragment = (ON_SubDMeshFragment*)m_unused_fragments[density]; m_unused_fragments[density] = m_unused_fragments[density]->m_next; - - if (nullptr != p) - { - for (/*empty int*/; p + m_sizeof_limit_curve <= p1; p += m_sizeof_limit_curve) - { - ON_FixedSizePoolElement* ele = (ON_FixedSizePoolElement*)p; - ele->m_next = m_unused_limit_curves; - m_unused_limit_curves = ele; - } - } } *fragment = src_fragment; @@ -1945,6 +2133,12 @@ bool ON_SubDHeap::ReturnMeshFragment(ON_SubDMeshFragment * fragment) case 17 * 17: // 16x16 mesh quad fragment i = 4; break; + case 33 * 33: // 32x32 mesh quad fragment + i = 5; + break; + case 65 * 65: // 64x64 mesh quad fragment + i = 6; + break; default: i = count; break; @@ -1952,8 +2146,12 @@ bool ON_SubDHeap::ReturnMeshFragment(ON_SubDMeshFragment * fragment) if (i >= count) return ON_SUBD_RETURN_ERROR(false); + ON_FixedSizePool& fsp + = (i == m_full_fragment_display_density) ? m_fsp_full_fragments + : (i+1 == m_full_fragment_display_density) ? m_fsp_part_fragments : m_fsp_oddball_fragments; + ON_FixedSizePoolElement* ele = (ON_FixedSizePoolElement*)fragment; - ON_SleepLockGuard guard(m_limit_block_pool); + ON_SleepLockGuard guard(fsp); ((unsigned int*)ele)[5] = 0; // zero m_vertex_count_etc and m_vertex_capacity_etc ele->m_next = m_unused_fragments[i]; m_unused_fragments[i] = ele; @@ -1987,51 +2185,34 @@ class ON_SubDEdgeSurfaceCurve* ON_SubDHeap::AllocateEdgeSurfaceCurve( { if (cv_capacity < 1 || cv_capacity > ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity) return ON_SUBD_RETURN_ERROR(nullptr); - if (0 == m_limit_block_pool.SizeofElement()) - Internal_InitializeLimitBlockPool(); - - ON_SubDEdgeSurfaceCurve* limit_curve; - double* cvx = nullptr; - + if (0 == this->m_fsp_limit_curves.SizeofElement()) { - ON_SleepLockGuard guard(m_limit_block_pool); - if ( - nullptr == m_unused_limit_curves - || ( cv_capacity > ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity && nullptr == m_unused_limit_curves->m_next) - ) - { - char* p = (char*)m_limit_block_pool.AllocateDirtyElement(); - if (nullptr == p) - return ON_SUBD_RETURN_ERROR(nullptr); - char* p1 = p + m_limit_block_pool.SizeofElement(); - while (p + m_sizeof_limit_curve < p1) - { - ON_FixedSizePoolElement* ele = (ON_FixedSizePoolElement*)p; - ele->m_next = m_unused_limit_curves; - m_unused_limit_curves = ele; - p += m_sizeof_limit_curve; - } - } - - limit_curve = (ON_SubDEdgeSurfaceCurve*)m_unused_limit_curves; - m_unused_limit_curves = m_unused_limit_curves->m_next; - if (cv_capacity > ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity) - { - cvx = (double*)m_unused_limit_curves; - m_unused_limit_curves = m_unused_limit_curves->m_next; - } + if( false == this->Internal_InitializeLimitCurvesPool()) + return ON_SUBD_RETURN_ERROR(nullptr); } - memset(limit_curve, 0, sizeof(*limit_curve)); - limit_curve->m_cv_capacity = ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity; - if (nullptr != cvx) + ON_SubDEdgeSurfaceCurve* limit_curve = nullptr; + double* cvx = nullptr; { - // increase capacity - limit_curve->m_cv_capacity = ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity; - limit_curve->m_cvx = cvx; - double* p1 = cvx + 3 * (ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity-ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity); - while (cvx < p1) - *cvx++ = ON_DBL_QNAN; + ON_SleepLockGuard guard(m_fsp_limit_curves); + limit_curve = (ON_SubDEdgeSurfaceCurve*)m_fsp_limit_curves.AllocateDirtyElement(); + if (cv_capacity > ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity) + cvx = (double*)m_fsp_limit_curves.AllocateDirtyElement(); + } + + if (nullptr != limit_curve) + { + memset(limit_curve, 0, sizeof(*limit_curve)); + limit_curve->m_cv_capacity = ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity; + if (nullptr != cvx) + { + // increase capacity + limit_curve->m_cv_capacity = ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity; + limit_curve->m_cvx = cvx; + double* p1 = cvx + 3 * (ON_SubDEdgeSurfaceCurve::MaximumControlPointCapacity - ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity); + while (cvx < p1) + *cvx++ = ON_DBL_QNAN; + } } return limit_curve; @@ -2091,7 +2272,7 @@ ON_SubDEdgeSurfaceCurve* ON_SubDHeap::CopyEdgeSurfaceCurve(const ON_SubDEdge* so memcpy(desination_curve->m_cv5, source_curve->m_cv5, sz5); if (cv_count > ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity && nullptr != desination_curve->m_cvx && nullptr != source_curve->m_cvx) { - const size_t szx = (cv_count - ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity) * 24; + const size_t szx = ((size_t)(cv_count - ON_SubDEdgeSurfaceCurve::MinimumControlPointCapacity)) * 24; memcpy(desination_curve->m_cvx, source_curve->m_cvx, szx); } desination_curve->m_cv_count = cv_count; @@ -2101,25 +2282,26 @@ ON_SubDEdgeSurfaceCurve* ON_SubDHeap::CopyEdgeSurfaceCurve(const ON_SubDEdge* so } bool ON_SubDHeap::ReturnEdgeSurfaceCurve( - class ON_SubDEdgeSurfaceCurve* limit_curve + ON_SubDEdgeSurfaceCurve* limit_curve ) { if (nullptr != limit_curve) { + // zero cv_count and cv_capacity - to limit crashes caused by rogue references limit_curve->m_cv_count = 0; + limit_curve->m_cv_capacity = 0; ON_FixedSizePoolElement* ele0 = (ON_FixedSizePoolElement*)limit_curve; ON_FixedSizePoolElement* ele1 = (ON_FixedSizePoolElement*)limit_curve->m_cvx; if (nullptr != ele1) { - ((unsigned int*)ele1)[2] = 0; // zero cv_count and cv_capacity - to limit crashes caused by rogue references - ele0->m_next = ele1; + // zero cv_count and cv_capacity - to limit crashes caused by rogue references + ((ON_SubDEdgeSurfaceCurve*)ele1)->m_cv_count = 0; + ((ON_SubDEdgeSurfaceCurve*)ele1)->m_cv_capacity = 0; } - else - ele1 = ele0; - ((unsigned int*)ele0)[2] = 0; // zero cv_count and cv_capacity - to limit crashes caused by rogue references - ON_SleepLockGuard guard(m_limit_block_pool); - ele1->m_next = m_unused_limit_curves; - m_unused_limit_curves = ele0; + ON_SleepLockGuard guard(m_fsp_limit_curves); + m_fsp_limit_curves.ReturnElement(ele0); + if (nullptr != ele1) + m_fsp_limit_curves.ReturnElement(ele1); } return true; } diff --git a/opennurbs_subd_iter.cpp b/opennurbs_subd_iter.cpp index 29e31c6d..65d16b17 100644 --- a/opennurbs_subd_iter.cpp +++ b/opennurbs_subd_iter.cpp @@ -292,6 +292,27 @@ const ON_SubDVertex* ON_SubDVertexIdIterator::NextVertex() return nullptr; } + +const ON_SubDVertex* ON_SubDVertexIdIterator::FirstVertexOnLevel(unsigned int level_index) +{ + for (const ON_SubDVertex* v = (const ON_SubDVertex*)FirstElement(); nullptr != v; v = (const ON_SubDVertex*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&v->m_id)[1] && level_index == v->SubdivisionLevel()) + return v; + } + return nullptr; +} + +const ON_SubDVertex* ON_SubDVertexIdIterator::NextVertexOnLevel(unsigned int level_index) +{ + for (const ON_SubDVertex* v = (const ON_SubDVertex*)NextElement(); nullptr != v; v = (const ON_SubDVertex*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&v->m_id)[1] && level_index == v->SubdivisionLevel()) + return v; + } + return nullptr; +} + const ON_SubDVertex* ON_SubDVertexIdIterator::CurrentVertex() const { return (const ON_SubDVertex*)CurrentElement(); @@ -310,10 +331,10 @@ void ON_SubDHeap::InitializeEdgeIdIterator( } void ON_SubDimple::InitializeEdgeIdIterator( - class ON_SubDEdgeIdIterator& vidit + class ON_SubDEdgeIdIterator& eidit ) const { - m_heap.InitializeEdgeIdIterator(vidit); + m_heap.InitializeEdgeIdIterator(eidit); } void ON_SubDEdgeIdIterator::Internal_Init() @@ -355,6 +376,26 @@ const ON_SubDEdge* ON_SubDEdgeIdIterator::NextEdge() return nullptr; } +const ON_SubDEdge* ON_SubDEdgeIdIterator::FirstEdgeOnLevel(unsigned int level_index) +{ + for (const ON_SubDEdge* e = (const ON_SubDEdge*)FirstElement(); nullptr != e; e = (const ON_SubDEdge*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&e->m_id)[1] && level_index == e->SubdivisionLevel()) + return e; + } + return nullptr; +} + +const ON_SubDEdge* ON_SubDEdgeIdIterator::NextEdgeOnLevel(unsigned int level_index) +{ + for (const ON_SubDEdge* e = (const ON_SubDEdge*)NextElement(); nullptr != e; e = (const ON_SubDEdge*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&e->m_id)[1] && level_index == e->SubdivisionLevel()) + return e; + } + return nullptr; +} + const ON_SubDEdge* ON_SubDEdgeIdIterator::CurrentEdge() const { return (const ON_SubDEdge*)CurrentElement(); @@ -366,17 +407,17 @@ const ON_SubDEdge* ON_SubDEdgeIdIterator::CurrentEdge() const // void ON_SubDHeap::InitializeFaceIdIterator( - class ON_SubDFaceIdIterator& vidit + class ON_SubDFaceIdIterator& fidit ) const { - vidit.ON_FixedSizePoolIterator::Create(&m_fspf); + fidit.ON_FixedSizePoolIterator::Create(&m_fspf); } void ON_SubDimple::InitializeFaceIdIterator( - class ON_SubDFaceIdIterator& vidit + class ON_SubDFaceIdIterator& fidit ) const { - m_heap.InitializeFaceIdIterator(vidit); + m_heap.InitializeFaceIdIterator(fidit); } void ON_SubDFaceIdIterator::Internal_Init() @@ -418,6 +459,27 @@ const ON_SubDFace* ON_SubDFaceIdIterator::NextFace() return nullptr; } +const ON_SubDFace* ON_SubDFaceIdIterator::FirstFaceOnLevel(unsigned int level_index) +{ + for (const ON_SubDFace* f = (const ON_SubDFace*)FirstElement(); nullptr != f; f = (const ON_SubDFace*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&f->m_id)[1] && level_index == f->SubdivisionLevel()) + return f; + } + return nullptr; +} + +const ON_SubDFace* ON_SubDFaceIdIterator::NextFaceOnLevel(unsigned int level_index) +{ + for (const ON_SubDFace* f = (const ON_SubDFace*)NextElement(); nullptr != f; f = (const ON_SubDFace*)NextElement()) + { + if (ON_UNSET_UINT_INDEX != (&f->m_id)[1] && level_index == f->SubdivisionLevel()) + return f; + } + return nullptr; +} + + const ON_SubDFace* ON_SubDFaceIdIterator::CurrentFace() const { return (const ON_SubDFace*)CurrentElement(); diff --git a/opennurbs_subd_texture.cpp b/opennurbs_subd_texture.cpp index 812b97f0..5c41c527 100644 --- a/opennurbs_subd_texture.cpp +++ b/opennurbs_subd_texture.cpp @@ -1046,6 +1046,23 @@ bool ON_SubD::HasPerFaceColorsFromPackId() const } + +void ON_SubD::SetPerFaceColorsFromSymmetryMotif() const +{ + if (FaceCount() < 1) + return; + + this->ClearPerFaceColors(); + ChangeRenderContentSerialNumber(); // face color changes. +} + +bool ON_SubD::HasPerFaceColorsFromSymmetryMotif() const +{ + return false; +} + + + ////////////////////////////////////////////////////////////////////////////// // // ON_SubDMeshFragment - texture coordinates diff --git a/opennurbs_symmetry.cpp b/opennurbs_symmetry.cpp index 9a25554e..a3e1e337 100644 --- a/opennurbs_symmetry.cpp +++ b/opennurbs_symmetry.cpp @@ -70,6 +70,131 @@ const ON_wString ON_Symmetry::SymmetryTypeToString(ON_Symmetry::Type symmetry_ty return ON_wString(s); } +ON_Symmetry::Region ON_Symmetry::SymmetryRegionFromUnsigned(unsigned int region_as_unsigned) +{ + switch (region_as_unsigned) + { + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::Unset); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::In); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationAxis); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnReflectionPlane); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationZeroPlane); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationOnePlane); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::InAndOut); + ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::Out); + } + + ON_ERROR("Invalid region_as_unsigned parameter"); + return ON_Symmetry::Region::Unset; +} + + +const ON_wString SymmetryRegionToString(ON_Symmetry::Region r) +{ + switch (r) + { + case ON_Symmetry::Region::Unset: + return ON_wString(L"Unset"); + break; + case ON_Symmetry::Region::In: + return ON_wString(L"In"); + break; + case ON_Symmetry::Region::OnRotationAxis: + return ON_wString(L"OnRotationAxis"); + break; + case ON_Symmetry::Region::OnReflectionPlane: + return ON_wString(L"OnReflectionPlane"); + break; + case ON_Symmetry::Region::OnRotationZeroPlane: + return ON_wString(L"OnRotationZeroPlane"); + break; + case ON_Symmetry::Region::OnRotationOnePlane: + return ON_wString(L"OnRotationOnePlane"); + break; + case ON_Symmetry::Region::InAndOut: + return ON_wString(L"InAndOut"); + break; + case ON_Symmetry::Region::Out: + return ON_wString(L"Out"); + break; + } + return ON_wString::EmptyString; +} + +bool ON_Symmetry::IsOn(ON_Symmetry::Region r, bool bInAndOutResult) +{ + const unsigned char max = bInAndOutResult ? static_cast(ON_Symmetry::Region::Out) : static_cast(ON_Symmetry::Region::InAndOut); + return static_cast(r) > static_cast(ON_Symmetry::Region::In) && static_cast(r) < max; +} + +bool ON_Symmetry::IsInOrOn(ON_Symmetry::Region r, bool bInAndOutResult) +{ + const unsigned char max = bInAndOutResult ? static_cast(ON_Symmetry::Region::Out) : static_cast(ON_Symmetry::Region::InAndOut); + return static_cast(r) >= static_cast(ON_Symmetry::Region::In) && static_cast(r) < max; +} + +bool ON_Symmetry::IsNotInOrOn(ON_Symmetry::Region r, bool bInAndOutResult) +{ + return ON_Symmetry::IsInOrOn(r,!bInAndOutResult) ? false : true; +} + +ON_Symmetry::Region ON_Symmetry::PointRegion(ON_3dPoint point, bool bUseCleanupTolerance) const +{ + if (point.IsValid()) + { + const double tol = bUseCleanupTolerance ? CleanupTolerance() : ON_Symmetry::ZeroTolerance; + + double h[2]; + switch (m_type) + { + case ON_Symmetry::Type::Reflect: + h[0] = ReflectionPlane().ValueAt(point); + if (h[0] < -tol) + return ON_Symmetry::Region::Out; + if (h[0] <= tol) + return ON_Symmetry::Region::OnReflectionPlane; + if (h[0] > tol) + return ON_Symmetry::Region::In; + break; + + case ON_Symmetry::Type::Rotate: + h[0] = RotationZeroPlane().ValueAt(point); + if (h[0] < -tol) + return ON_Symmetry::Region::Out; + h[1] = RotationOnePlane().ValueAt(point); + if (h[1] < -tol) + return ON_Symmetry::Region::Out; + if (h[0] >= -tol && h[1] >= -tol) + { + // h[0] and h[1] are not nans ... + if (h[0] <= tol) + return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationAxis : ON_Symmetry::Region::OnRotationZeroPlane; + return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationOnePlane : ON_Symmetry::Region::In; + } + break; + + case ON_Symmetry::Type::ReflectAndRotate: + h[0] = ReflectionPlane().ValueAt(point); + if (h[0] < -tol) + return ON_Symmetry::Region::Out; + h[1] = RotationZeroPlane().ValueAt(point); + if (h[1] < -tol) + return ON_Symmetry::Region::Out; + if (h[0] >= -tol && h[1] >= -tol) + { + // h[0] and h[1] are not nans ... + if (h[0] <= tol) + return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationAxis : ON_Symmetry::Region::OnReflectionPlane; + return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationZeroPlane : ON_Symmetry::Region::In; + } + break; + } + } + + // When the point is not valid, the symmetry is not set, or an evaluaton produces nans, then return ON_Symmetry::Region::Unset. + return ON_Symmetry::Region::Unset; +} + ON_Symmetry::Coordinates ON_Symmetry::SymmetryCoordinatesFromUnsigned(unsigned int symmetry_coordinates_as_unsigned) { @@ -107,7 +232,7 @@ const ON_wString ON_Symmetry::SymmetryCoordinatesToString(ON_Symmetry::Coordinat bool ON_Symmetry::Write(ON_BinaryArchive& archive) const { - if (false == archive.BeginWrite3dmAnonymousChunk(3)) + if (false == archive.BeginWrite3dmAnonymousChunk(4)) return false; bool rc = false; @@ -129,26 +254,29 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const if (false == archive.WriteUuid(m_id)) break; - if (archive.BeginWrite3dmAnonymousChunk(1)) + if (archive.BeginWrite3dmAnonymousChunk(2)) { switch (m_type) { case ON_Symmetry::Type::Unset: break; case ON_Symmetry::Type::Reflect: - rc = archive.WritePlaneEquation(m_reflection_plane); + rc = archive.WritePlaneEquation(m_fixed_plane); break; case ON_Symmetry::Type::Rotate: - rc = archive.WriteLine(m_rotation_axis); + // fixed plane added for chunk version >= 2 + rc = archive.WriteLine(m_rotation_axis) && archive.WritePlaneEquation(m_fixed_plane); break; case ON_Symmetry::Type::ReflectAndRotate: - rc = archive.WritePlaneEquation(m_reflection_plane) && archive.WriteLine(m_rotation_axis); + rc = archive.WritePlaneEquation(m_fixed_plane) && archive.WriteLine(m_rotation_axis); break; case ON_Symmetry::Type::Inversion: - rc = archive.WriteXform(m_inversion_transform); + // fixed plane added for chunk version >= 2 + rc = archive.WriteXform(m_inversion_transform) && archive.WritePlaneEquation(m_fixed_plane); break; case ON_Symmetry::Type::Cyclic: - rc = archive.WriteXform(m_cyclic_transform); + // fixed plane added for chunk version >= 2 + rc = archive.WriteXform(m_cyclic_transform) && archive.WritePlaneEquation(m_fixed_plane); break; default: ON_ERROR("You added a new enum value but failed to update archive IO code."); @@ -164,8 +292,15 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const if (false == archive.WriteChar(ucoordinates)) break; - // ON_Symmetry::Coordinates added Feb 11, 2020 chunk version 3 - if ( false == archive.WriteBigInt(SymmetricObjectContentSerialNumber()) ) + // m_symmetric_object_content_serial_number added Feb 11, 2020 chunk version 3 + if ( false == archive.WriteBigInt(this->m_symmetric_object_content_serial_number) ) + break; + + // m_symmetric_object_topology_hash added March 29, 2021 chunk version 4 + if (false == this->m_symmetric_object_topology_hash.Write(archive)) + break; + // m_symmetric_object_geometry_hash added March 29, 2021 chunk version 4 + if (false == this->m_symmetric_object_geometry_hash.Write(archive)) break; rc = true; @@ -176,6 +311,381 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const return rc; } +bool ON_Symmetry::IsValidCyclicTranformation( + ON_Xform transformation, + unsigned transformation_order +) +{ + if (transformation_order < 2) + return false; + if (transformation_order > ON_Symmetry::MaximumOrder) + return false; + if (false == transformation.IsValid()) + return false; + + // In the comments below, + // I = identity transformation, + // n = transformation_order, + // T = transformation. + // + // + // Verify I = T^n, I != T^i when i maxd) + maxd = d0; + else if (d0 != d0) + maxd = ON_DBL_QNAN; + + const double d1 = fabs(e.ValueAt(P[i])); + if (d1 > maxd) + maxd = d1; + else if (d1 != d1) + maxd = ON_DBL_QNAN; + + const ON_3dPoint Q = transformation * P[i]; + const double d2 = (P[i] - Q).MaximumCoordinate(); + if (d2 > maxd) + maxd = d2; + else if (d2 != d2) + maxd = ON_DBL_QNAN; + } + + return maxd <= ON_Symmetry::ZeroTolerance; + +} + +bool ON_Symmetry::IsValidReflectionTranformationAndFixedPlane(ON_Xform reflection, ON_PlaneEquation reflection_plane) +{ + return ON_Symmetry::IsValidCyclicTranformation(reflection, 2) && ON_Symmetry::IsValidFixedPlane(reflection, reflection_plane); +} + +bool ON_Symmetry::IsMotifBoundarySubDVertex(const class ON_SubDVertex* v, bool bUseCleanupTolerance) const +{ + if (nullptr == v) + return false; + if (false == v->IsCreaseOrCorner()) + return false; + if (false == v->HasBoundaryVertexTopology()) + { + const ON_Symmetry::Type symmetry_type = this->SymmetryType(); + if (ON_Symmetry::Type::Reflect == symmetry_type) + return false; // easy case. + else if (ON_Symmetry::Type::Rotate == symmetry_type) + { + if (v->HasInteriorVertexTopology()) + return false; + if (ON_SubDVertexTag::Corner != v->m_vertex_tag) + return false; + // We have to keep "funky" corners - RH-63789 + } + else if (ON_Symmetry::Type::ReflectAndRotate == symmetry_type) + { + // Probably much harder than this - future work + return false; // easy case. + } + else + return false; + } + + const ON_3dPoint P = v->ControlNetPoint(); + const double tol = bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance; + double d; + switch (this->SymmetryType()) + { + case ON_Symmetry::Type::Reflect: + d = fabs(this->ReflectionPlane().ValueAt(P)); + break; + case ON_Symmetry::Type::Rotate: + // All boundary vertices must be eligable for joining. + // The pinwheel motifs in RH-63376 show why. + d = P.IsValid() ? 0.0 : ON_DBL_QNAN; + break; + case ON_Symmetry::Type::ReflectAndRotate: + // to be determined when ReflectAndRotate support is added + d = ON_DBL_QNAN; + break; + default: + d = ON_DBL_QNAN; + break; + } + + return (d <= tol); +} + +bool ON_Symmetry::IsValidRotationAxisAndFixedPlane( + ON_Line rotation_axis, + unsigned int rotation_count, + ON_PlaneEquation fixed_plane +) +{ + for (;;) + { + if (rotation_count < 2) + return false; + if (rotation_count > ON_Symmetry::MaximumOrder) + return false; + if (false == rotation_axis.IsValid()) + break; + if (false == (rotation_axis.Length() > ON_Symmetry::ZeroTolerance)) + break; + + if (false == fixed_plane.IsSet()) + break; + + // Test both fixed_plane and a unitized version just in case + // fixed_plane has a very short (x,y,z) part. + // The idea of all symmetry validation is to prohibit getting + // started with any sort of garbage input. + const ON_PlaneEquation e = fixed_plane.UnitizedPlaneEquation(); + const double h[] = + { + fixed_plane.ValueAt(rotation_axis.from), + fixed_plane.ValueAt(rotation_axis.to), + e.ValueAt(rotation_axis.from), + e.ValueAt(rotation_axis.to) + }; + + double maxd = 0.0; + for (size_t i = 0; i < sizeof(h)/sizeof(h[0]) && maxd <= ON_Symmetry::ZeroTolerance; ++i) + { + const double d = fabs(h[i]); + if (d > maxd) + maxd = d; + else if (d != d) + maxd = ON_DBL_QNAN; + } + if (maxd <= ON_Symmetry::ZeroTolerance) + return true; + + // The rotation axis must lie in the fixed plane. + break; + } + return false; +} + + + + +static bool Internal_CreateAndValidateFixedPlane( + ON_3dPoint P, + ON_3dVector N, + ON_PlaneEquation& fixed_plane, + const double zero_tolerance, + const size_t Fcount, + const ON_3dPoint* F +) +{ + for (;;) + { + // create + if (false == N.IsUnitVector()) + N = N.UnitVector(); + if (false == fixed_plane.Create(P, N)) + break; + if (false == fixed_plane.IsValid()) + break; + + // validate + double d = 0.0; + for (size_t i = 0; i < Fcount && d <= zero_tolerance; ++i) + d = fabs(fixed_plane.ValueAt(F[i])); + + if (d <= zero_tolerance) + return true; // no nans and all F[] close enough to fixed_plane. + + break; + } + + // P and N do not define a valid fixed plane + fixed_plane = ON_PlaneEquation::NanPlaneEquation; + return false; +} + +static bool Internal_InventSymmetryFixedPlane(const double zero_tolerance, ON_Xform xform, unsigned xform_order, ON_PlaneEquation& fixed_plane) +{ + for(;;) + { + if (xform_order < 2) + break; + if (false == xform.IsNotIdentity()) + break; + + // Set F[] = some fixed points of the xform. + ON_3dPoint P[] = { ON_3dPoint(0,0,0), ON_3dPoint(1,0,0), ON_3dPoint(0,1,0), ON_3dPoint(0,0,1) }; + ON_3dPoint F[sizeof(P) / sizeof(P[0])]; + const size_t Pcount = sizeof(P) / sizeof(P[0]); + for (size_t i = 0; i < Pcount; ++i) + F[i] = P[i]; + ON_Xform x(xform); + for (unsigned j = 1; j < xform_order; ++j) + { + for (size_t i = 0; i < Pcount; ++i) + F[i] += x * P[i]; + x = x * xform; + } + if (false == x.IsIdentity(zero_tolerance)) + break; + + // validate fixed points + double fdist = 0.0; + for (size_t i = 0; i < Pcount; ++i) + { + F[i] = F[i] / ((double)xform_order); + double d = F[i].DistanceTo(xform * F[i]); + if (d > fdist) + fdist = d; + else if (d != d) + { + fdist = ON_DBL_QNAN; + break; + } + } + if (false == (fdist <= zero_tolerance)) + break; // xform doesn't have fixed points. + + size_t i0 = 0; + size_t j0 = 0; + double maxd = 0.0; + for (size_t i = 1; i < Pcount; ++i) + { + const double d = F[0].DistanceTo(F[i]); + if (d >= maxd) + { + maxd = d; + j0 = i0; + i0 = i; + } + else if (maxd != maxd) + { + maxd = ON_DBL_QNAN; + break; + } + } + if (maxd != maxd) + break; // nan + + if (maxd <= zero_tolerance) + { + // xform has a fixed point (like DiagonalTransformation(neg,neg,neg)). + if (Internal_CreateAndValidateFixedPlane(F[0], ON_3dVector::ZAxis, fixed_plane,zero_tolerance,Pcount,F) ) + return true; + break; + } + + if (0 == i0) + break; + + const ON_Line fixed_line(F[0], F[i0]); + if (false == fixed_line.IsValid()) + break; + const ON_3dVector T = fixed_line.Tangent(); + + // fixed_line is in the fixed plane + maxd = 0.0; + for (size_t i = 1; i < Pcount; ++i) + { + const ON_3dPoint Q = fixed_line.ClosestPointTo(F[i]); + const double d = F[i].DistanceTo(Q); + if (d > maxd) + maxd = d; + else if (d != d) + { + maxd = ON_DBL_QNAN; + break; + } + } + if (maxd != maxd) + break; // nan + + + if (maxd <= zero_tolerance) + { + // xform has a fixed line (like a rotation) + if ( Internal_CreateAndValidateFixedPlane(F[0], T.Perpendicular(ON_3dVector::NanVector), fixed_plane, zero_tolerance, Pcount, F) ) + return true; + break; + } + + if (0 == j0 || i0 == j0) + break; + + const ON_3dVector Y = F[j0] - F[0]; + const ON_3dVector N = ON_3dVector::CrossProduct(T, Y).UnitVector(); + if (Internal_CreateAndValidateFixedPlane(F[0], N, fixed_plane, zero_tolerance, Pcount, F)) + return true; + + break; + } + + fixed_plane = ON_PlaneEquation::NanPlaneEquation; + return false; +} + +static bool Internal_InventRotationFixedPlane(const double zero_tolerance, const ON_Line rotation_axis, unsigned cyclic_order, ON_PlaneEquation& fixed_plane) +{ + const ON_3dPoint F[2] = { rotation_axis.from, rotation_axis.to }; + const ON_3dVector N = rotation_axis.Tangent().Perpendicular(ON_3dVector::NanVector); + return Internal_CreateAndValidateFixedPlane(F[0], N, fixed_plane, zero_tolerance, 2, F); +} bool ON_Symmetry::Read(ON_BinaryArchive& archive) { @@ -191,7 +701,7 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive) ON_UUID symmetry_id = ON_nil_uuid; ON_Xform inversion_transform = ON_Xform::Nan; ON_Xform cyclic_transform = ON_Xform::Nan; - ON_PlaneEquation reflection_plane = ON_PlaneEquation::NanPlaneEquation; + ON_PlaneEquation fixed_plane = ON_PlaneEquation::NanPlaneEquation; ON_Line rotation_axis = ON_Line::NanLine; bool rc = false; @@ -231,33 +741,45 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive) break; case ON_Symmetry::Type::Reflect: - rc = archive.ReadPlaneEquation(reflection_plane); + rc = archive.ReadPlaneEquation(fixed_plane); if (rc) - symmetry = ON_Symmetry::CreateReflectSymmetry(reflection_plane, symmetry_coordinates); + symmetry = ON_Symmetry::CreateReflectSymmetry(fixed_plane, symmetry_coordinates); break; case ON_Symmetry::Type::Rotate: rc = archive.ReadLine(rotation_axis); + if (inner_chunk_version >= 2) + rc = archive.ReadPlaneEquation(fixed_plane); + else + rc = Internal_InventRotationFixedPlane(ON_Symmetry::ZeroTolerance, rotation_axis, cyclic_order, fixed_plane); if (rc) - symmetry = ON_Symmetry::CreateRotateSymmetry(rotation_axis, cyclic_order, symmetry_coordinates); + symmetry = ON_Symmetry::CreateRotateSymmetry(rotation_axis, cyclic_order, fixed_plane, symmetry_coordinates); break; case ON_Symmetry::Type::ReflectAndRotate: - rc = archive.ReadPlaneEquation(reflection_plane) && archive.ReadLine(rotation_axis); + rc = archive.ReadPlaneEquation(fixed_plane) && archive.ReadLine(rotation_axis); if (rc) - symmetry = ON_Symmetry::CreateReflectAndRotateSymmetry(reflection_plane, rotation_axis, cyclic_order, symmetry_coordinates); + symmetry = ON_Symmetry::CreateReflectAndRotateSymmetry(fixed_plane, rotation_axis, cyclic_order, symmetry_coordinates); break; case ON_Symmetry::Type::Inversion: rc = archive.ReadXform(inversion_transform); + if (inner_chunk_version >= 2) + rc = archive.ReadPlaneEquation(fixed_plane); + else + rc = Internal_InventSymmetryFixedPlane(ON_Symmetry::ZeroTolerance, inversion_transform, 2, fixed_plane); if (rc) - symmetry = ON_Symmetry::CreateInversionSymmetry(symmetry_id, inversion_transform, symmetry_coordinates); + symmetry = ON_Symmetry::Internal_CreateInversionSymmetry(symmetry_id, inversion_transform, fixed_plane, symmetry_coordinates); break; case ON_Symmetry::Type::Cyclic: rc = archive.ReadXform(cyclic_transform); + if (inner_chunk_version >= 2) + rc = archive.ReadPlaneEquation(fixed_plane); + else + rc = Internal_InventSymmetryFixedPlane(ON_Symmetry::ZeroTolerance, cyclic_transform, cyclic_order, fixed_plane); if (rc) - symmetry = ON_Symmetry::CreateCyclicSymmetry(symmetry_id, cyclic_transform, cyclic_order, symmetry_coordinates); + symmetry = ON_Symmetry::Internal_CreateCyclicSymmetry(symmetry_id, cyclic_transform, cyclic_order, fixed_plane, symmetry_coordinates); break; default: @@ -299,7 +821,16 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive) ON__UINT64 symmetric_object_content_serial_number = 0; rc = archive.ReadBigInt(&symmetric_object_content_serial_number); if (rc) - SetSymmetricObjectContentSerialNumber(symmetric_object_content_serial_number); + this->m_symmetric_object_content_serial_number = symmetric_object_content_serial_number; + + if (chunk_version < 4) + break; + + // m_symmetric_object_topology_hash added March 29, 2021 chunk version 4 + rc = rc && this->m_symmetric_object_topology_hash.Read(archive); + + // m_symmetric_object_geometry_hash added March 29, 2021 chunk version 4 + rc = rc && this->m_symmetric_object_geometry_hash.Read(archive); break; } @@ -333,6 +864,14 @@ void ON_PlaneEquation::Dump(class ON_TextLog& text_log) const } void ON_Symmetry::Dump(ON_TextLog& text_log) const +{ + ToText(true, text_log); +} + +void ON_Symmetry::ToText( + bool bIncludeSymmetricObject, + class ON_TextLog& text_log +) const { const ON_wString type = ON_Symmetry::SymmetryTypeToString(m_type); const ON_wString coordinates = ON_Symmetry::SymmetryCoordinatesToString(m_coordinates); @@ -387,7 +926,7 @@ void ON_Symmetry::Dump(ON_TextLog& text_log) const case ON_Symmetry::Type::Inversion: { const ON_Line line = RotationAxis(); - text_log.Print(InversionTransform()); + text_log.Print(InversionTransformation()); text_log.PrintNewLine(); } break; @@ -395,7 +934,7 @@ void ON_Symmetry::Dump(ON_TextLog& text_log) const case ON_Symmetry::Type::Cyclic: { const ON_Line line = RotationAxis(); - text_log.Print(CyclicTransform()); + text_log.Print(CyclicTransformation()); text_log.PrintNewLine(); } break; @@ -403,6 +942,17 @@ void ON_Symmetry::Dump(ON_TextLog& text_log) const default: break; } + + if (bIncludeSymmetricObject && 0 != m_symmetric_object_content_serial_number) + { + text_log.Print("Symmetric object hashes:\n"); + const ON_TextLogIndent indent1(text_log); + text_log.Print(L"content serial number: %u\n", m_symmetric_object_content_serial_number); + text_log.PrintString(ON_wString(L"topology hash: ") + this->m_symmetric_object_topology_hash.ToStringEx(true)); + text_log.PrintNewLine(); + text_log.PrintString(ON_wString(L"geometry hash: ") + this->m_symmetric_object_geometry_hash.ToStringEx(true)); + text_log.PrintNewLine(); + } } const ON_Symmetry ON_Symmetry::TransformConditionally(const ON_Xform& xform) const @@ -422,9 +972,9 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c case ON_Symmetry::Type::Reflect: { - if (false == m_reflection_plane.IsValid()) + if (false == m_fixed_plane.IsValid()) break; - ON_PlaneEquation e = m_reflection_plane; + ON_PlaneEquation e = m_fixed_plane; e.Transform(xform); if (false == e.IsValid()) break; @@ -440,17 +990,23 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c a.Transform(xform); if (false == a.IsValid()) break; - return ON_Symmetry::CreateRotateSymmetry(a, RotationCount(), m_coordinates); + if (false == m_fixed_plane.IsValid()) + break; + ON_PlaneEquation e = m_fixed_plane; + e.Transform(xform); + if (false == e.IsValid()) + break; + return ON_Symmetry::CreateRotateSymmetry(a, RotationCount(), e, m_coordinates); } break; case ON_Symmetry::Type::ReflectAndRotate: { - if (false == m_reflection_plane.IsValid()) + if (false == m_fixed_plane.IsValid()) break; if (false == m_rotation_axis.IsValid()) break; - ON_PlaneEquation e = m_reflection_plane; + ON_PlaneEquation e = m_fixed_plane; e.Transform(xform); if (false == e.IsValid()) break; @@ -465,16 +1021,28 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c case ON_Symmetry::Type::Inversion: { const ON_Xform xform_inverse = xform.Inverse(); - const ON_Xform inversion_xform = xform * InversionTransform()*xform_inverse; - return ON_Symmetry::CreateInversionSymmetry(SymmetryId(), inversion_xform, m_coordinates); + const ON_Xform inversion_xform = xform * InversionTransformation()*xform_inverse; + if (false == m_fixed_plane.IsValid()) + break; + ON_PlaneEquation e = m_fixed_plane; + e.Transform(xform); + if (false == e.IsValid()) + break; + return ON_Symmetry::Internal_CreateInversionSymmetry(SymmetryId(), inversion_xform, e, m_coordinates); } break; case ON_Symmetry::Type::Cyclic: { const ON_Xform xform_inverse = xform.Inverse(); - const ON_Xform cyclic_xform = xform * CyclicTransform()*xform_inverse; - return ON_Symmetry::CreateCyclicSymmetry(SymmetryId(), cyclic_xform, CyclicOrder(), m_coordinates); + const ON_Xform cyclic_xform = xform * CyclicTransformation()*xform_inverse; + if (false == m_fixed_plane.IsValid()) + break; + ON_PlaneEquation e = m_fixed_plane; + e.Transform(xform); + if (false == e.IsValid()) + break; + return ON_Symmetry::Internal_CreateCyclicSymmetry(SymmetryId(), cyclic_xform, CyclicOrder(), e, m_coordinates); } break; @@ -533,9 +1101,9 @@ static bool Internal_SameTransformation(const ON_Symmetry* lhs, const ON_Symmetr return false; if (lhs->CyclicOrder() != rhs->CyclicOrder()) return false; - if (lhs->InversionOrder() > 1 && false == Internal_SameTransformation(lhs->InversionTransform(), rhs->InversionTransform(), zero_tolerance)) + if (lhs->InversionOrder() > 1 && false == Internal_SameTransformation(lhs->InversionTransformation(), rhs->InversionTransformation(), zero_tolerance)) return false; - if (lhs->CyclicOrder() > 1 && false == Internal_SameTransformation(lhs->CyclicTransform(), rhs->CyclicTransform(), zero_tolerance)) + if (lhs->CyclicOrder() > 1 && false == Internal_SameTransformation(lhs->CyclicTransformation(), rhs->CyclicTransformation(), zero_tolerance)) return false; return true; } @@ -590,22 +1158,44 @@ int ON_Symmetry::CompareSymmetryTransformation(const ON_Symmetry* lhs, const ON_ } const ON_Symmetry ON_Symmetry::CreateInversionSymmetry( + ON_UUID symmetry_id, + ON_Xform inversion_transform, + ON_Symmetry::Coordinates symmetry_coordinates +) +{ + ON_ERROR("Use CreateRotationSymmetry(axis,2,...) or CreateReflectionSymmetry()"); + return ON_Symmetry::Unset; +} + +const ON_PlaneEquation ON_Symmetry::Internal_UnitizePlaneEquationParameter(ON_PlaneEquation e) +{ + for (;;) + { + if (false == e.IsSet()) + break; + if (e.IsUnitized()) + return e; + const ON_PlaneEquation u = e.UnitizedPlaneEquation(); + if (u.IsUnitized()) + return u; + break; + } + return ON_PlaneEquation::NanPlaneEquation; +} + +const ON_Symmetry ON_Symmetry::Internal_CreateInversionSymmetry( ON_UUID symmetry_id, ON_Xform inversion_transform, + ON_PlaneEquation fixed_plane, ON_Symmetry::Coordinates symmetry_coordinates ) { for (;;) { - if (false == inversion_transform.IsValid()) - break; - const double det = inversion_transform.Determinant(); if (false == (det < 0.0)) break; - - ON_Xform x = inversion_transform* inversion_transform; - if (false == x.IsIdentity(ON_Symmetry::ZeroTolerance)) + if (false == ON_Symmetry::IsValidCyclicTranformation(inversion_transform, 2)) break; if (false == (ON_nil_uuid == symmetry_id) ) @@ -620,12 +1210,13 @@ const ON_Symmetry ON_Symmetry::CreateInversionSymmetry( } ON_Symmetry symmetry; - symmetry.m_type = ON_Symmetry::Type::Cyclic; + symmetry.m_type = ON_Symmetry::Type::Inversion; symmetry.m_coordinates = symmetry_coordinates; symmetry.m_inversion_order = 2; symmetry.m_cyclic_order = 1; symmetry.m_id = symmetry_id; symmetry.m_inversion_transform = inversion_transform; + symmetry.m_fixed_plane = fixed_plane; symmetry.m_cyclic_transform = ON_Xform::IdentityTransformation; return symmetry; } @@ -634,20 +1225,33 @@ const ON_Symmetry ON_Symmetry::CreateInversionSymmetry( } + const ON_Symmetry ON_Symmetry::CreateCyclicSymmetry( - ON_UUID symmetry_id, + ON_UUID symmetry_id, ON_Xform cyclic_transform, unsigned int cyclic_order, ON_Symmetry::Coordinates symmetry_coordinates ) +{ + ON_ERROR("Use CreateRotationSymmetry() or CreateReflectionSymmetry()"); + return ON_Symmetry::Unset; +} + +const ON_Symmetry ON_Symmetry::Internal_CreateCyclicSymmetry( + ON_UUID symmetry_id, + ON_Xform cyclic_transform, + unsigned int cyclic_order, + ON_PlaneEquation zero_plane, + ON_Symmetry::Coordinates symmetry_coordinates +) { for (;;) { - if (cyclic_order < 2) + zero_plane = ON_Symmetry::Internal_UnitizePlaneEquationParameter(zero_plane); + if (false == zero_plane.IsSet()) break; - if (cyclic_order > ON_Symmetry::MaximumOrder) - break; - if (false == cyclic_transform.IsValid()) + + if (false == ON_Symmetry::IsValidCyclicTranformation(cyclic_transform, cyclic_order)) break; const double det = cyclic_transform.Determinant(); @@ -662,18 +1266,6 @@ const ON_Symmetry ON_Symmetry::CreateCyclicSymmetry( break; } - unsigned n = 1; - ON_Xform x = cyclic_transform; - while (n < cyclic_order && x.IsValid() && false == x.IsIdentity(ON_Symmetry::ZeroTolerance)) - { - x = cyclic_transform * x; - ++n; - } - if (n != cyclic_order) - break; - if (false == x.IsIdentity(ON_Symmetry::ZeroTolerance)) - break; - if (false == (ON_nil_uuid == symmetry_id)) { // prohibit using built-in ids @@ -693,6 +1285,7 @@ const ON_Symmetry ON_Symmetry::CreateCyclicSymmetry( symmetry.m_id = symmetry_id; symmetry.m_inversion_transform = ON_Xform::IdentityTransformation; symmetry.m_cyclic_transform = cyclic_transform; + symmetry.m_fixed_plane = zero_plane; return symmetry; } @@ -706,16 +1299,20 @@ const ON_Symmetry ON_Symmetry::CreateReflectSymmetry( { for (;;) { - if (false == reflection_plane.IsValid()) + reflection_plane = ON_Symmetry::Internal_UnitizePlaneEquationParameter(reflection_plane); + if (false == reflection_plane.IsSet()) + break; + ON_PlaneEquation e = reflection_plane.UnitizedPlaneEquation(); + if (false == e.IsSet()) break; const ON_Xform xform(ON_Xform::MirrorTransformation(reflection_plane)); - ON_Symmetry symmetry = ON_Symmetry::CreateInversionSymmetry(ON_nil_uuid, xform, symmetry_coordinates); - if (ON_Symmetry::Type::Cyclic != symmetry.m_type) + if (false == ON_Symmetry::IsValidReflectionTranformationAndFixedPlane(xform, reflection_plane)) + break; + ON_Symmetry symmetry = ON_Symmetry::Internal_CreateInversionSymmetry(ON_nil_uuid, xform, reflection_plane, symmetry_coordinates); + if (ON_Symmetry::Type::Inversion != symmetry.m_type) break; symmetry.m_type = ON_Symmetry::Type::Reflect; - symmetry.m_coordinates = symmetry_coordinates; symmetry.m_id = ON_Symmetry::ReflectId; - symmetry.m_reflection_plane = reflection_plane; return symmetry; } return ON_Symmetry::Unset; @@ -723,22 +1320,32 @@ const ON_Symmetry ON_Symmetry::CreateReflectSymmetry( const ON_Symmetry ON_Symmetry::CreateRotateSymmetry( ON_Line rotation_axis, - unsigned int rotation_count, + unsigned int rotation_count, + ON_Symmetry::Coordinates symmetry_coordinates +) +{ + // You must use the version that has a rotation_plane parameter. + ON_ERROR("Obsolete function."); + return ON_Symmetry::Unset; +} + +const ON_Symmetry ON_Symmetry::CreateRotateSymmetry( + ON_Line rotation_axis, + unsigned int rotation_count, + ON_PlaneEquation zero_rotation_plane, ON_Symmetry::Coordinates symmetry_coordinates ) { for (;;) { - if (rotation_count < 2 || rotation_count > ON_Symmetry::MaximumOrder) - break; - if (false == rotation_axis.IsValid()) + zero_rotation_plane = ON_Symmetry::Internal_UnitizePlaneEquationParameter(zero_rotation_plane); + if (false == ON_Symmetry::IsValidRotationAxisAndFixedPlane(rotation_axis, rotation_count, zero_rotation_plane)) break; const ON_Xform R = Internal_RotationXform(rotation_axis, 1, rotation_count); - ON_Symmetry symmetry = ON_Symmetry::CreateCyclicSymmetry(ON_nil_uuid, R, rotation_count, symmetry_coordinates); + ON_Symmetry symmetry = ON_Symmetry::Internal_CreateCyclicSymmetry(ON_nil_uuid, R, rotation_count, zero_rotation_plane, symmetry_coordinates); if (ON_Symmetry::Type::Cyclic != symmetry.m_type) break; symmetry.m_type = ON_Symmetry::Type::Rotate; - symmetry.m_coordinates = symmetry_coordinates; symmetry.m_id = ON_Symmetry::RotateId; symmetry.m_rotation_axis = rotation_axis; return symmetry; @@ -756,23 +1363,13 @@ const ON_Symmetry ON_Symmetry::CreateReflectAndRotateSymmetry( { for (;;) { - if (false == reflection_plane.IsValid()) - break; - if (false == rotation_axis.IsValid()) - break; - - // rotation axis must be in the reflection plane - const double h0 = reflection_plane.ValueAt(rotation_axis.from); - const double h1 = reflection_plane.ValueAt(rotation_axis.to); - if (false == (fabs(h0) <= ON_ZERO_TOLERANCE)) - break; - if (false == (fabs(h1) <= ON_ZERO_TOLERANCE)) - break; + reflection_plane = ON_Symmetry::Internal_UnitizePlaneEquationParameter(reflection_plane); const ON_Symmetry reflection = CreateReflectSymmetry(reflection_plane, symmetry_coordinates); if (ON_Symmetry::Type::Reflect != reflection.SymmetryType()) break; - const ON_Symmetry rotation = CreateRotateSymmetry(rotation_axis,rotation_count, symmetry_coordinates); + + const ON_Symmetry rotation = CreateRotateSymmetry(rotation_axis,rotation_count, reflection_plane, symmetry_coordinates); if (ON_Symmetry::Type::Rotate != rotation.SymmetryType()) break; @@ -784,7 +1381,7 @@ const ON_Symmetry ON_Symmetry::CreateReflectAndRotateSymmetry( symmetry.m_id = ON_Symmetry::ReflectAndRotateId; symmetry.m_inversion_transform = reflection.m_inversion_transform; symmetry.m_cyclic_transform = rotation.m_cyclic_transform; - symmetry.m_reflection_plane = reflection.m_reflection_plane; + symmetry.m_fixed_plane = reflection.m_fixed_plane; symmetry.m_rotation_axis = rotation.m_rotation_axis; return symmetry; } @@ -856,9 +1453,9 @@ int ON_Symmetry::Compare(const ON_Symmetry* lhs, const ON_Symmetry* rhs) if (0U == lhs->m_inversion_order || 0U == lhs->m_cyclic_order) return 0; - if (ON_Symmetry::Type::Reflect == lhs->m_type || ON_Symmetry::Type::ReflectAndRotate == lhs->m_type ) + if (ON_Symmetry::Type::Unset != lhs->m_type) { - const int rc = ON_Symmetry::Internal_CompareDouble(&lhs->m_reflection_plane.x, &rhs->m_reflection_plane.x, 4); + const int rc = ON_Symmetry::Internal_CompareDouble(&lhs->m_fixed_plane.x, &rhs->m_fixed_plane.x, 4); if (0 != rc) return rc; } @@ -891,6 +1488,8 @@ int ON_Symmetry::Compare(const ON_Symmetry* lhs, const ON_Symmetry* rhs) return rc; } + // Do NOT compare the symmetric object settings in this function. + return 0; } @@ -946,16 +1545,28 @@ unsigned int ON_Symmetry::CyclicOrder() const return m_cyclic_order; } -const ON_Xform ON_Symmetry::InversionTransform() const +const ON_Xform ON_Symmetry::InversionTransformation() const { return IsSet() ? m_inversion_transform : ON_Xform::Nan; } -const ON_Xform ON_Symmetry::CyclicTransform() const +const ON_Xform ON_Symmetry::InversionTransform() const +{ + // OBSOLETE use ON_Symmetry::InversionTransformation() + return ON_Xform::Nan; +} + +const ON_Xform ON_Symmetry::CyclicTransformation() const { return IsSet() ? m_cyclic_transform : ON_Xform::Nan; } +const ON_Xform ON_Symmetry::CyclicTransform() const +{ + // OBSOLETE use ON_Symmetry::CyclicTransformation() + return ON_Xform::Nan; +} + const ON_SHA1_Hash ON_Symmetry::Hash() const { for(;;) @@ -974,8 +1585,8 @@ const ON_SHA1_Hash ON_Symmetry::Hash() const sha1.AccumulateInteger32(InversionOrder()); sha1.AccumulateInteger32(CyclicOrder()); - if (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type) - sha1.AccumulateDoubleArray(4, &m_reflection_plane.x); + if (ON_Symmetry::Type::Unset != m_type) + sha1.AccumulateDoubleArray(4, &m_fixed_plane.x); if (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type) sha1.AccumulateDoubleArray(6, &m_rotation_axis.from.x); @@ -996,7 +1607,21 @@ const ON_SHA1_Hash ON_Symmetry::Hash() const const ON_PlaneEquation ON_Symmetry::ReflectionPlane() const { return (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type) - ? m_reflection_plane + ? FixedPlane() + : ON_PlaneEquation::NanPlaneEquation; +} + +const ON_Xform ON_Symmetry::ReflectionTransformation() const +{ + return (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type) + ? MotifTransformation(1) + : ON_Xform::Nan; +} + +const ON_PlaneEquation ON_Symmetry::FixedPlane() const +{ + return (ON_Symmetry::Type::Unset != m_type) + ? m_fixed_plane : ON_PlaneEquation::NanPlaneEquation; } @@ -1049,6 +1674,58 @@ double ON_Symmetry::RotationAngleRadians() const : ON_DBL_QNAN; } +const ON_PlaneEquation ON_Symmetry::RotationZeroPlane() const +{ + if (ON_Symmetry::Type::Rotate == m_type) + { + return this->m_fixed_plane; + } + if (ON_Symmetry::Type::ReflectAndRotate == m_type) + { + const double a = RotationAngleRadians(); + if (a > 0.0 && a <= ON_PI) + { + ON_Xform rot; + rot.Rotation(-0.5*a, this->RotationAxisDirection(), this->RotationAxisPoint()); + ON_PlaneEquation e(this->m_fixed_plane); + e.Transform(rot); + return e.NegatedPlaneEquation(); + } + } + return ON_PlaneEquation::NanPlaneEquation; +} + + +const ON_PlaneEquation ON_Symmetry::RotationOnePlane() const +{ + if (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type) + { + const ON_PlaneEquation r0 = RotationZeroPlane(); + if (r0.IsSet()) + { + const ON_PlaneEquation r1 = (RotationTransformation() * r0).NegatedPlaneEquation(); + if (r1.IsSet()) + return r1; + } + } + + return ON_PlaneEquation::NanPlaneEquation; +} + + +const ON_Xform ON_Symmetry::RotationTransformation() const +{ + if (ON_Symmetry::Type::Rotate == m_type) + { + return MotifTransformation(1); + } + if (ON_Symmetry::Type::ReflectAndRotate == m_type) + { + return MotifTransformation(2); + } + return ON_Xform::Nan; +} + const ON_Xform ON_Symmetry::Internal_RotationXform( int rotation_index, int rotation_count @@ -1194,6 +1871,24 @@ const ON_Xform ON_Symmetry::MotifTransformation( return x; } +unsigned ON_Symmetry::GetMotifTransformations( + bool bIncludeIdentity, + ON_SimpleArray& motif_transformations +) const +{ + motif_transformations.SetCount(0); + const unsigned motif_count = MotifCount(); + if (motif_count <= 0) + return 0; + motif_transformations.Reserve(bIncludeIdentity ? motif_count : (motif_count - 1)); + for (unsigned i = bIncludeIdentity ? 0 : 1; i < motif_count; ++i) + { + const ON_Xform x = this->MotifTransformation(i); + motif_transformations.Append(x); + } + return motif_count; +} + const ON_Xform ON_Symmetry::Internal_ReflectAndRotateTransformation(unsigned index) const { ON_Xform r = Internal_RotationXform(index / 2, m_cyclic_order); @@ -1203,6 +1898,11 @@ const ON_Xform ON_Symmetry::Internal_ReflectAndRotateTransformation(unsigned ind } ON_SHA1_Hash ON_Symmetry::Sha1Hash() const +{ + return SymmetryHash(); +} + +const ON_SHA1_Hash ON_Symmetry::SymmetryHash() const { ON_SHA1 sha1; sha1.AccumulateBytes(&m_type, sizeof(m_type)); @@ -1212,7 +1912,7 @@ ON_SHA1_Hash ON_Symmetry::Sha1Hash() const sha1.AccumulateId(m_id); sha1.AccumulateDoubleArray(16, &m_inversion_transform.m_xform[0][0]); sha1.AccumulateDoubleArray(16, &m_cyclic_transform.m_xform[0][0]); - sha1.AccumulateDoubleArray(4,&m_reflection_plane.x); + sha1.AccumulateDoubleArray(4,&m_fixed_plane.x); sha1.Accumulate3dPoint(m_rotation_axis.from); sha1.Accumulate3dPoint(m_rotation_axis.to); return sha1.Hash(); @@ -1220,19 +1920,47 @@ ON_SHA1_Hash ON_Symmetry::Sha1Hash() const void ON_Symmetry::SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const { - if (0 == symmetric_object_content_serial_number) - ClearSymmetricObjectContentSerialNumber(); // so a debugger breakpoint can be set in one place to watching clearing - else - m_symmetric_object_content_serial_number = symmetric_object_content_serial_number; + // OBSOLETE - You must use SetSymmetricObject(). + ON_ERROR("Obsolete function."); + + ClearSymmetricObject(); // strongly discourage use by erasing all SymmetricContent settings. +} + +void ON_Symmetry::ClearSymmetricObject() const +{ + m_symmetric_object_content_serial_number = 0U; + m_symmetric_object_geometry_hash = ON_SHA1_Hash::ZeroDigest; + m_symmetric_object_topology_hash = ON_SHA1_Hash::ZeroDigest; } void ON_Symmetry::ClearSymmetricObjectContentSerialNumber() const { - m_symmetric_object_content_serial_number = 0U; + ClearSymmetricObject(); } ON__UINT64 ON_Symmetry::SymmetricObjectContentSerialNumber() const { - return m_symmetric_object_content_serial_number; + // OBSOLETE - You must use SameSymmetricObjectGeometry(). + ON_ERROR("Obsolete function."); + return 0; } +void ON_Symmetry::SetCleanupTolerance( + double cleanup_tolerance +) +{ + if (cleanup_tolerance > ON_Symmetry::ZeroTolerance && cleanup_tolerance < ON_UNSET_POSITIVE_VALUE) + m_cleanup_tolerance = cleanup_tolerance; + else + m_cleanup_tolerance = 0.0; // See ON_Symmetry::CleanupTolerance(). +} + + +double ON_Symmetry::CleanupTolerance() const +{ + // The default constructor sets m_cleanup_tolerance = 0.0. + // Handling m_cleanup_tolerance this way insures that ON_Symmetry::CleanupTolerance() + // will always return ON_Symmetry::ZeroTolerance (which may chnage), even with class definitions + // read from old archives. + return (m_cleanup_tolerance >= ON_Symmetry::ZeroTolerance) ? m_cleanup_tolerance : ON_Symmetry::ZeroTolerance; +} diff --git a/opennurbs_symmetry.h b/opennurbs_symmetry.h index 535e378a..f46e259f 100644 --- a/opennurbs_symmetry.h +++ b/opennurbs_symmetry.h @@ -107,16 +107,204 @@ public: World = 2, }; + /// + /// ON_Symmetry::Region specifies various subsets of the primary motif region. + /// + enum class Region : unsigned char { + + /// + /// Unset or unknown location + /// + Unset = 0, + + /// + /// Inside the primary motif region. + /// + In = 1, + + /// + /// On the rotation axis. Applies to Reflect and ReflectAndRotate symmetries. + /// + OnRotationAxis = 2, + + /// + /// On the reflection plane. Applies to Reflect and ReflectAndRotate symmetries. + /// + OnReflectionPlane = 3, + + /// + /// On the zero angle plane. Applies to Reflect and ReflectAndRotate symmetries. + /// + OnRotationZeroPlane = 4, + + /// + /// On the the rotation of the zero angle plane. Applies to Rotate symmetries. + /// + OnRotationOnePlane = 5, + + /// + /// Something like a line segment that has points both inside and outside the primary motif region. + /// + InAndOut = 6, + + /// + /// Outside the primary motif region. + /// + Out = 7 + }; + + static ON_Symmetry::Region SymmetryRegionFromUnsigned(unsigned int region_as_unsigned); + + static const ON_wString SymmetryRegionToString(ON_Symmetry::Region r); + + /* + Returns: + (r == ON_Symmetry::Region::OnRotationZeroPlane || r == ON_Symmetry::Region::OnRotationOnePlane). + */ + static bool SymmetryRegionIsRotationPlane(ON_Symmetry::Region r); + + /* + Paramaters: + r - [in] + bInAndOutResult - [in] + Value to return when ON_Symmetry::Region::InandOut == r. + Returns: + True if r is OnRotationAxis, OnReflectionPlane, OnZeroAnglePlane, or OnOneAnglePlane. + */ + static bool IsOn(ON_Symmetry::Region r, bool bInAndOutResult); + + /* + Paramaters: + r - [in] + bInAndOutResult - [in] + Value to return when ON_Symmetry::Region::InandOut == r. + Returns: + ON_Symmetry::Region::In == r || IsOn(r). + */ + static bool IsInOrOn(ON_Symmetry::Region r, bool bInAndOutResult); + + /* + Paramaters: + r - [in] + bInAndOutResult - [in] + Value to return when ON_Symmetry::Region::InandOut == r. + Returns: + !ON_Symmetry::IsInOrOn(r) + */ + static bool IsNotInOrOn(ON_Symmetry::Region r, bool bInAndOutResult); + + /* + Parameters: + point - [in] + point to test. + bUseCleanupTolerance - [in] + The tolerance used to dermine if a point is On an axis or plane is + bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance. + When in doubt, pass false. + Returns: + The location of the point releative to the primary motif regions. + */ + ON_Symmetry::Region PointRegion(ON_3dPoint point, bool bUseCleanupTolerance) const; + static ON_Symmetry::Coordinates SymmetryCoordinatesFromUnsigned(unsigned int coordinates_as_unsigned); static const ON_wString SymmetryCoordinatesToString( ON_Symmetry::Coordinates symmetry_coordinates ); + + /* + Parameters: + v - [in] + vertex to test + bUseCleanupTolerance - [in] + The tolerance used to dermine if a point is On an axis or plane is + bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance. + When in doubt, pass false. + Returns: + True if v is a vertex that might possibly be merged when + transformed motifs are joined to create a symmetric SubD. + */ + bool IsMotifBoundarySubDVertex(const class ON_SubDVertex* v, bool bUseCleanupTolerance) const; + + static const ON_UUID ReflectId; static const ON_UUID RotateId; static const ON_UUID ReflectAndRotateId; + /* + Description: + Test a cyclic transformation to make sure it is stable enough to be used in an ON_Symmetry. + Parameters: + transformation - [in] + transformation_order - [in] + Returns: + True if these 2 conditions are satisified. + 1. identity = transformation^transformation_order. + 2. identity != transformation^i for 1 <= i < transformation_order. + Remarks: + The value ON_Symmetry::ZeroTolerance is used for all "zero tolerances." + */ + static bool IsValidCyclicTranformation( + ON_Xform transformation, + unsigned transformation_order + ); + + /* + Description: + Test a transformation and transformation to make sure the transformation fixes + points on the plane. + Parameters: + transformation - [in] + fixed_plane - [in] + Returns: + True if the the transformation and fixed_plane are valid and + the transformation fixes points on the plane. + Remarks: + The value ON_Symmetry::ZeroTolerance is used for all "zero tolerances." + */ + static bool IsValidFixedPlane( + ON_Xform transformation, + ON_PlaneEquation fixed_plane + ); + + + /* + Description: + Test a transformation and reflection plane. + Parameters: + reflection - [in] + reflection_plane - [in] + Returns: + True if these 3 conditions are satisified. + 1. identity = reflection^2 + 2. identity != reflection + 3. Points on the reflection_plane are fixed. + */ + static bool IsValidReflectionTranformationAndFixedPlane( + ON_Xform reflection, + ON_PlaneEquation reflection_plane + ); + + /* + Parameters: + rotation_axis - [in] + rotation_count - [in] + fixed_plane - [in] + Returns: + True if these 3 conditions are satisified. + 1. rotation_axis is a valid line. + 2. rotation_count > 0 + 3. fixed_plane contains the rotation axis. + Remarks: + The value ON_Symmetry::ZeroTolerance is used for all "zero tolerances." + */ + static bool IsValidRotationAxisAndFixedPlane( + ON_Line rotation_axis, + unsigned int rotation_count, + ON_PlaneEquation fixed_plane + ); + /* Description: Reflection about a plane. @@ -124,42 +312,56 @@ public: Points on the reflection plane are fixed. Parameters: reflection_plane - [in] + A point P is in the primary motif when reflection_plane.ValueAt(P) >= 0. symmetry_coordinates - [in] object or world. Example: If the reflection_plane is the y-z plane, then (x,y,z) -> (-x,y,z) -> (x,y,z). Remarks: - InversionTransform() = reflection. - CyclicTransform() = identity. + InversionTransformation() = reflection. + CyclicTransformation() = identity. */ static const ON_Symmetry CreateReflectSymmetry( ON_PlaneEquation reflection_plane, ON_Symmetry::Coordinates symmetry_coordinates ); + ON_DEPRECATED_MSG("Does nothing. Use ON_Symmetry::CreateRotateSymmetry(axis,count,plane,coordinates.") + static const ON_Symmetry CreateRotateSymmetry( + ON_Line rotation_axis, + unsigned int rotation_count, + ON_Symmetry::Coordinates symmetry_coordinates + ); + /* Description: Rotation around an axis. - The symmetric object has reflection copies of the motif. + The symmetric object has reflection_count copies of the motif. Points on the axis are fixed. Parameters: rotation_axis - [in] rotation_count - [in] rotation_count must be >= 2 and the rotation angle is (360/rotation_count) degrees. + zero_rotation_plane - [in] + zero_rotation_plane plane is used to define the primary motif region. + A point P is in the primary motif when zero_rotation_plane.ValueAt(P) >= 0 && (MotifTransformation(1)*zero_rotation_plane).ValueAt(P) <= 0. + The rotation axis must be in zero_rotation_plane (zero_rotation_plane.ValueAt(rotation_axis) <= ON_ZERO_TOLERANCE and zero_rotation_plane.ValueAt(rotation_axis) <= ON_ZERO_TOLERANCE). symmetry_coordinates - [in] object or world. Example: - If the rotation axis is the z-axis and the order is N, then + If the rotation axis is the z-axis and the order is N, then the generator is then (x,y,z) -> (r*cos(a), r*sin(a), z) -> (r*cos(2*a), r*sin(2*a), z) -> ... -> (x,y,z), where r = sqrt(x*x + y*y) and a = (360 degrees)/N. + Any plane containing the z-axis can be used as the zero_rotation_plane. Remarks: - CyclicTransform() = rotation. - InversionTransform() = identity. + CyclicTransformation() = rotation. + InversionTransformation() = identity. */ static const ON_Symmetry CreateRotateSymmetry( - ON_Line rotation_axis, + ON_Line rotation_axis, unsigned int rotation_count, + ON_PlaneEquation zero_rotation_plane, ON_Symmetry::Coordinates symmetry_coordinates ); @@ -181,8 +383,8 @@ public: and rotation_count = 2, then (x,y,z) -> (-x,y,z) -> (-x-y,z) -> (x,-y,z) -> (x,y,z). Remarks: - InversionTransform() = reflection. - CyclicTransform() = rotation by (360/rotation_count) degrees. + InversionTransformation() = reflection. + CyclicTransformation() = rotation by (360/rotation_count) degrees. */ static const ON_Symmetry CreateReflectAndRotateSymmetry( ON_PlaneEquation reflection_plane, @@ -191,6 +393,22 @@ public: ON_Symmetry::Coordinates symmetry_coordinates ); + ON_DEPRECATED_MSG("Use CreateRotationSymmetry(axis,2,...) or CreateReflectionSymmetry()") + static const ON_Symmetry CreateInversionSymmetry( + ON_UUID symmetry_id, + ON_Xform inversion_transform, + ON_Symmetry::Coordinates symmetry_coordinates + ); + + ON_DEPRECATED_MSG("Use CreateRotationSymmetry() or CreateReflectionSymmetry()") + static const ON_Symmetry CreateCyclicSymmetry( + ON_UUID symmetry_id, + ON_Xform cyclic_transform, + unsigned int cyclic_order, + ON_Symmetry::Coordinates symmetry_coordinates + ); + +private: /* Description: Create an inversion symmetry from a transformation. @@ -199,17 +417,20 @@ public: symmetry_id - [in] An id you can assign to the symmetry inversion_transform - [in] + fixed_plane - [in] inversion_transform^2 = identity. Det(inversion_transform) = -1. + inversion_transform must fix the plane specified by fixed_plane. symmetry_coordinates - [in] object or world. Remarks: If inversion_transform is a reflection, consider using CreateReflectSymmetry() instead. When Det(transformation) = 1 and transformtion^2 = identity, use CreateCyclicSymmetry() instead. */ - static const ON_Symmetry CreateInversionSymmetry( - ON_UUID symmetry_id, + static const ON_Symmetry Internal_CreateInversionSymmetry( + ON_UUID symmetry_id, ON_Xform inversion_transform, + ON_PlaneEquation fixed_plane, ON_Symmetry::Coordinates symmetry_coordinates ); @@ -227,6 +448,10 @@ public: Otherwise Det(cyclic_transform) = 1. cyclic_order - [in] cyclic_order >= 2 + zero_plane - [in] + zero_plane must contain all fixed points of cyclic_transform. + For any point P, F = P + cyclic_transform*P + ... + cyclic_transform^(cyclic_order-1)*P + is a fixed point of cyclic_transform. symmetry_coordinates - [in] object or world. Remarks: @@ -234,13 +459,21 @@ public: If cyclic_transform is a reflection, use CreateReflectionSymmetry(). If 2 = cyclic_order and Det(cyclic_transform) = -1, use CreateInversionSymmetry(). */ - static const ON_Symmetry CreateCyclicSymmetry( - ON_UUID symmetry_id, - ON_Xform cyclic_transform, + static const ON_Symmetry Internal_CreateCyclicSymmetry( + ON_UUID symmetry_id, + ON_Xform cyclic_transform, unsigned int cyclic_order, + ON_PlaneEquation zero_plane, ON_Symmetry::Coordinates symmetry_coordinates ); +public: + /* + Returns: + 0 if lhs and rhs define identical symmetries (types and transformations). + Remarks: + The symmtric object information is not compared. + */ static int Compare(const ON_Symmetry* lhs, const ON_Symmetry* rhs); /* @@ -298,10 +531,10 @@ public: /* Returns: 0: unset symmetry - 1: InversionTransform() = identity - 2: InversionTransform()^2 = identity and InversionTransform() != identity + 1: InversionTransformation() = identity + 2: InversionTransformation()^2 = identity and InversionTransform() != identity Remarks: - In common cases, InversionTransform() is either the identity or a reflection. + In common cases, InversionTransformation() is either the identity or a reflection. */ unsigned int InversionOrder() const; @@ -309,9 +542,9 @@ public: Returns: 0: unset symmetry 1: the cyclic transformation is the identity. - N >= 2: CyclicTransform()^N = idenity and CyclicTransform()^i != idenity when 0 < i < N. + N >= 2: CyclicTransformation()^N = idenity and CyclicTransform()^i != idenity when 0 < i < N. Remarks: - In common cases, CyclicTransform() is either the identity or a rotation. + In common cases, CyclicTransformation() is either the identity or a rotation. */ unsigned int CyclicOrder() const; @@ -321,6 +554,7 @@ public: Parameters: index - [in] 0 based index of the copy (negative values are supported) + 0 returns the identity transformation. Remarks: "0 based" means when index is a multiple of MotifCount(), the identity is returned. */ @@ -328,36 +562,69 @@ public: int index ) const; + /* + Description: + Get the set of motif transformations that maps the starting motif to the copies. + Parameters: + bIncludeIdentity - [in] + If true, then motif_transformations[0] = identity and MotifCount() transformations are returned. + Otherwise, motif_transformations[0] = MotifeTransformation(1) and (MotifCount() -1) transformations are returned. + motif_transformations[] - out + motif transformations are returned here. + Returns: + MotifCount(). + */ + unsigned GetMotifTransformations( + bool bIncludeIdentity, + ON_SimpleArray& motif_transformations + ) const; + /* Returns: The inversion transformation is returned. - InversionTransform()^InversionOrder() = identity. + InversionTransformation()^InversionOrder() = identity. Remarks: - In common cases, InversionTransform() is either the identity or a reflection (mirror). + In common cases, InversionTransformation() is either the identity or a reflection (mirror). Remarks: NOTE: A symmetry with SymmetryOrder() = 2 and transformation S can be represented - as either InversionTransform() = S and CyclicTransform() = identity or - or CyclicTransform() = S and InversionTransform() = idenity. + as either InversionTransformation() = S and CyclicTransformation() = identity or + or CyclicTransformation() = S and InversionTransformation() = idenity. The convention is to use the cyclic transforms when S is a 180 rotations and inversion transforms otherwise. */ + const ON_Xform InversionTransformation() const; + + ON_DEPRECATED_MSG("Returns nans. Call InversionTransformation()") const ON_Xform InversionTransform() const; /* Returns: The cyclic transformation is returned. - CyclicTransform()^CyclicOrder() = identity. + CyclicTransformation()^CyclicOrder() = identity. Remarks: - In common cases, CyclicTransform() is either the identity or a rotation. + In common cases, CyclicTransformation() is either the identity or a rotation. Remarks: NOTE: A symmetry with SymmetryOrder() = 2 and transformation S can be represented - as either InversionTransform() = S and CyclicTransform() = identity or - or CyclicTransform() = S and InversionTransform() = idenity. + as either InversionTransformation() = S and CyclicTransformation() = identity or + or CyclicTransformation() = S and InversionTransformation() = idenity. The convention is to use the cyclic transforms when S is a 180 rotations and inversion transforms otherwise. */ + const ON_Xform CyclicTransformation() const; + + ON_DEPRECATED_MSG("Return nans. Call CyclicTransformation()") const ON_Xform CyclicTransform() const; + /* + Returns: + A plane containing all the fixed points of the symmetry. + When the symmetry type is Reflect, FixedPlane() = ReflectionPlane(). + When the symmetry type is Rotate, FixedPlane() contains the axis of rotation + and defines the zero angle. + When the symmetry type is ReflectAndRotate, FixedPlane() = ReflectionPlane(). + */ + const ON_PlaneEquation FixedPlane() const; + /* Returns: A SHA1 hash uniquely identifying the symmetry @@ -371,6 +638,13 @@ public: */ const ON_PlaneEquation ReflectionPlane() const; + /* + Returns: + If the symmetry is type is Reflect or ReflectAndRotate, then the reflection plane is returned. + Othewise ON_Plane::Nan is returned. + */ + const ON_Xform ReflectionTransformation() const; + /* Returns: If the symmetry is type is Rotate or ReflectAndRotate, then the rotation axis is returned. @@ -417,6 +691,13 @@ public: */ double RotationAngleDegrees() const; + /* + Returns: + If the symmetry is type is Rotate or ReflectAndRotate, then the rotation transformation is returned. + Otherwise ON_Xform::Nan is returned. + */ + const ON_Xform RotationTransformation() const; + /* Returns: If the symmetry is type is Rotate or ReflectAndRotate, then the rotation angle in radians is returned. @@ -426,10 +707,38 @@ public: */ double RotationAngleRadians() const; + /* + Returns: + If the symmetry is type is Rotate or ReflectAndRotate, then the plane defining the zero angle or rotation is returned. + Othewise ON_PlaneEquation::NanPlaneEquation is returned. + Remarks: + For a Rotate symmetry, the region on or above RotationZeroPlane() and above RotationOnePlane() + is the default primary motif region. + For a RotateAndReflect symmetry, the region on or above RotationZeroPlane() and on or above ReflectionPlane() + is the default primary motif region. + */ + const ON_PlaneEquation RotationZeroPlane() const; + + /* + Returns: + If the symmetry is type is Rotate or ReflectAndRotate, then (RotationTransformation() * RotationOnePlane()).NegatedPlaneEquation() is returned. + Othewise ON_PlaneEquation::NanPlaneEquation is returned. + Remarks: + For a Rotate symmetry, the region on or above RotationZeroPlane() and above RotationOnePlane() + is the default primary motif region. + For a RotateAndReflect symmetry, the region on or above RotationZeroPlane() and on or above ReflectionPlane() + is the default primary motif region. + */ + const ON_PlaneEquation RotationOnePlane() const; + public: bool Write(class ON_BinaryArchive&) const; bool Read(class ON_BinaryArchive&); void Dump(class ON_TextLog&) const; + void ToText( + bool bIncludeSymmetricObject, + class ON_TextLog& text_log + ) const; /* Description: @@ -449,32 +758,127 @@ public: Returns: A SHA1 hash value that uniquely identifies the symmetry settings. Remarks: - The symmetric object content serial number is not incuded in the hash. + The symmetric object content serial number and symmetric object hashes + are not incuded in the symmetry hash. */ + const ON_SHA1_Hash SymmetryHash() const; + + ON_DEPRECATED_MSG("Use SymmetryHash()") ON_SHA1_Hash Sha1Hash() const; /* Description: - Set the mutable value returned by SymmetricObjectContentSerialNumber(). + If the SubD is known to have the symmetry specified by this ON_Symmetry, + then SetSymmetricObject() is used to save the subd's geometry and topology + hash on this ON_Symmetry. + Parameters: + subd - [in] + This subd must have the symmetry specified by this ON_Symmetry. + Returns: + True if the symmetric object state was saved on this. + False if input is not valid. + Remarks: + At a later time and after unknown modifications or copying, use + SameSymmetricObjectGeometry(subd1) and SameSymmetricObjectTopology(subd1) + to determine if subd1 has the same geometry or topology as subd at the + time SetSymmetryObject(sub) was called. */ - void SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const; + bool SetSymmetricObject(const class ON_SubD* subd) const; /* Description: - Set the mutable value returned by SymmetricObjectContentSerialNumber() to 0. + If the parent SubD is known to have the symmetry specified by this ON_Symmetry, + then SetSymmetricObject() is used to save the subd's geometry and topology + hash on this ON_Symmetry. + Parameters: + subdimple - [in] + The parent SubD must have the symmetry specified by this ON_Symmetry. + Returns: + True if the symmetric object state was saved on this. + False if input is not valid. + Remarks: + The ON_SubDimple version is used internally. Pretend this doesn't exist. + + At a later time and after unknown modifications or copying, use + SameSymmetricObjectGeometry(subd1) and SameSymmetricObjectTopology(subd1) + to determine if subd1 has the same geometry or topology as subd at the + time SetSymmetryObject(sub) was called. */ - void ClearSymmetricObjectContentSerialNumber() const; + bool SetSymmetricObject(const class ON_SubDimple* subdimple) const; + + /* + Description: + Clears the symmetric object geometry and topology hashes. + */ + void ClearSymmetricObject() const; /* Returns: - Set a runtime serial number that corresponded to the content of the symmetric object - after it the application verified it was symmetric. + True if subd has identical geometry to the subd passed to SetSymmetricObject(). */ + bool SameSymmetricObjectGeometry(const class ON_SubD* subd) const; + + bool SameSymmetricObjectGeometry(const class ON_SubDimple* subdimple) const; + + /* + Returns: + True if subd has identical topology to the subd passed to SetSymmetricObject(). + */ + bool SameSymmetricObjectTopology(const class ON_SubD* subd) const; + + bool SameSymmetricObjectTopology(const class ON_SubDimple* subdimple) const; + + ON_DEPRECATED_MSG("OBSOLETE AND DESTROYS SYMMETRIC OBJECT INFORMATION. Use SetSymmetricObject()") + void SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const; + + ON_DEPRECATED_MSG("Use ClearSymmetricObject()") + void ClearSymmetricObjectContentSerialNumber() const; + + ON_DEPRECATED_MSG("ALWAYS RETURNS ZERO. Use SameSymmetricObjectGeometry(...)") ON__UINT64 SymmetricObjectContentSerialNumber() const; -private: +public: + // ON_Symmetry::ZeroTolerance is a 3d tolerance use to validate + // transformations, rotation axis locations, and plane equations. + // This tolerance is constant across all applications and used to validate symmetric objects. + // Use the per symmetry ON_Symmetry.CleanupTolerance() to prepare input objects. + // In a valid symmetric object, the distance between distinct SubD vertex locations + // must always be greater than ON_Symmetry::ZeroTolerance. static const double ZeroTolerance; - + +public: + /* + Description: + Cleanup tolerance is a 3d world unt length used to preprocess input when creating + symmetric objects. For example, in a relfect symmetry, cleanup tolerance would be + used to move vertices near the reflection plane to be on the reflection plane. + This tolerance is context sensitive. In the same model it could vary between two + different input objects. The value is saved with the symmetry so calculations driven + by modifying an input object can be repeated using the same cleanup tolerance. + Parameters: + cleanup_tolerance - [in] + cleanup_tolerance >= ON_Symmetry::ZeroTolerance. + The cleanup tolerance for this specific symmetry. The default is ON_Symmetry::ZeroTolerance. + */ + void SetCleanupTolerance( + double cleanup_tolerance + ); + + /* + Description: + Cleanup tolerance is a 3d world unt length used to preprocess input when creating + symmetric objects. For example, in a relfect symmetry, cleanup tolerance would be + used to move vertices near the reflection plane to be on the reflection plane. + This tolerance is context sensitive. In the same model it could vary between two + different input objects. The value is saved with the symmetry so calculations driven + by modifying an input object can be repeated using the same cleanup tolerance. + Returns: + The cleanup tolerance for this specific symmetry. It is always >= ON_Symmetry::ZeroTolerance. + The default is ON_Symmetry::ZeroTolerance. + */ + double CleanupTolerance() const; + +private: ON_Symmetry::Type m_type = ON_Symmetry::Type::Unset; ON_Symmetry::Coordinates m_coordinates = ON_Symmetry::Coordinates::Unset; @@ -487,8 +891,6 @@ private: // m_cyclic_order (0 = unset, 1 = identity (no cyclic), >= 2 cyclic order (non-identity cyclic) unsigned int m_cyclic_order = 0; - mutable ON__UINT64 m_symmetric_object_content_serial_number = 0; - // id is a preset value for the 3 built in symmetries and user defined for others ON_UUID m_id = ON_nil_uuid; @@ -498,25 +900,56 @@ private: // m_cyclic_transform^m_cyclic_order = identity ON_Xform m_cyclic_transform = ON_Xform::Nan; - // Set when type is Reflect or ReflectAndRotate - ON_PlaneEquation m_reflection_plane = ON_PlaneEquation::NanPlaneEquation; + // m_fixed_plane contains all fixed points of the symmetry transformation. + // When type is Reflect, m_fixed_plane is the reflection plane. + // When type is Rotate, m_fixed_plane contains the rotation axis and is used to define zero rotation angle. + // When type is ReflectAndRotate, m_fixed_plane is the reflection plane. + ON_PlaneEquation m_fixed_plane = ON_PlaneEquation::NanPlaneEquation; // Set when type is Rotate or ReflectAndRotate + // m_rotation_axis always lies in m_plane. ON_Line m_rotation_axis = ON_Line::NanLine; + // Using 0.0 insures the default returned by CleanupTolerance() is alwasy ON_Symmetry::ZeroTolerance. + double m_cleanup_tolerance = 0.0; + private: - ON__UINT_PTR m_reservedX = 0; - ON__UINT_PTR m_reservedY = 0; - double m_reservedA = 0.0; - double m_reservedB = 0.0; - double m_reservedC = 0.0; - double m_reservedD = 0.0; + /////////////////////////////////////////////////////////////////////////// + // + // NOTE WELL: + // Avoid the temptation to provide SDK access to any of the m_symmetric_object* values. + // + // Use one of the ON_Symmetry::SetSymmetricObject(...) overrides to set these. + // Use ON_Symmetry::ClearSymmetricObject() to clear the m_symmetric_object* values. + // + // Use one of the ON_Symmetry::SameSymmetricObjectGeometry(...) to see if the geometry + // of the object you have is has the symmetry defined by this ON_Symmetry. + // + // Use one of the ON_Symmetry::SameSymmetricObjectTopology(...) to see if the topology + // of the object you have is has the symmetry defined by this ON_Symmetry. + + // The triplet + // m_symmetric_object_content_serial_number, + // m_symmetric_object_topology_hash, + // m_symmetric_object_geometry_hash + // is used to detect if a previously symmtetric object is still symmetric + // and to determine how it should be updated to return to being symmetric. + // Legacy 3dm files have only the m_symmetric_object_content_serial_number. + // Rhino 7.6 and later files have the SHA1 hashes as well. + mutable ON__UINT64 m_symmetric_object_content_serial_number = 0; + // For ON_SubD, this is the value of ON_SubD.TopologyHash() at the time the SubD + // was known to be perfectly symmetric. + mutable ON_SHA1_Hash m_symmetric_object_topology_hash = ON_SHA1_Hash::ZeroDigest; + // For ON_SubD, this is the value of ON_SubD.GeometryHash() at the time the SubD + // was known to be perfectly symmetric. + mutable ON_SHA1_Hash m_symmetric_object_geometry_hash = ON_SHA1_Hash::ZeroDigest; private: static int Internal_CompareDouble(const double* lhs, const double* rhs, size_t count); const ON_Xform Internal_ReflectAndRotateTransformation(unsigned index) const; static const ON_Xform Internal_RotationXform(ON_Line axis, int rotation_index, int rotation_count); const ON_Xform Internal_RotationXform(int rotation_index, int rotation_count) const; + static const ON_PlaneEquation Internal_UnitizePlaneEquationParameter(ON_PlaneEquation); }; #endif diff --git a/opennurbs_system_runtime.h b/opennurbs_system_runtime.h index 83d1b87b..683b73ef 100644 --- a/opennurbs_system_runtime.h +++ b/opennurbs_system_runtime.h @@ -100,13 +100,15 @@ #if !defined(ON_RUNTIME_APPLE_IOS) #define ON_RUNTIME_APPLE_MACOS +#if !defined(RHINO_CORE_COMPONENT) // Apple: // Defines RHINO_CORE_COMPONENT here. // If we publish an Apple C++ pubic SDK, this will need to be adjusted. // Windows: // uses the property sheet RhinoProjectPropertySheets/Rhino.Cpp.common.props // Some build products in Windows are not "core components" -#define RHINO_CORE_COMPONENT = 1 +#define RHINO_CORE_COMPONENT 1 +#endif #endif #if (defined(__LP64__) || defined(__ppc64__)) diff --git a/opennurbs_text.cpp b/opennurbs_text.cpp index a733b41f..03563571 100644 --- a/opennurbs_text.cpp +++ b/opennurbs_text.cpp @@ -2178,7 +2178,7 @@ bool ON_TextContent::FormatTolerance( ON_DimStyle::suppress_zero zs = alt ? dimstyle->AlternateZeroSuppress() : dimstyle->ZeroSuppress(); - double length_factor = dimstyle->LengthFactor(); + double length_factor = 1.0; // dimstyle->LengthFactor(); RH-63775 double unit_length_factor = ON::UnitScale(units_in, dim_us); length_factor *= unit_length_factor; if (alt) diff --git a/opennurbs_text_style.cpp b/opennurbs_text_style.cpp index d9fd33cb..ff11c6bf 100644 --- a/opennurbs_text_style.cpp +++ b/opennurbs_text_style.cpp @@ -153,8 +153,18 @@ static void SetNameFromFontDescription( const ON_ComponentManifest& manifest, ON_TextStyle& text_style ) -{ - const ON_wString font_description(text_style.Font().Description()); +{ + // Dale Lear RH-63824 May 3, 2021 + // It is critical that bIncludeNotOnDevice be set to false. + // Otherwise missing fonts will have a description beginning with "[Not on device]" + // and square brackets are not permitted in names. + // This code is inventing a Rhino 6/7 dimstyle name from a V4 text style. + // The text style names were unreliable in V4 and we've used the font + // description as a proxy for years now. + const bool bIncludeNotOnDevice = false; + + const ON_wString font_description(text_style.Font().Description(ON_Font::NameLocale::LocalizedFirst, ON_wString::HyphenMinus, ON_wString::Space, true, bIncludeNotOnDevice)); + ON_wString text_style_name = manifest.UnusedName( text_style.ComponentType(), ON_nil_uuid, diff --git a/opennurbs_textiterator.cpp b/opennurbs_textiterator.cpp index 89d466f4..a1309b36 100644 --- a/opennurbs_textiterator.cpp +++ b/opennurbs_textiterator.cpp @@ -855,7 +855,7 @@ ON_TextRunBuilder::ON_TextRunBuilder( bool underlined = dimstyle->Font().IsUnderlined(); bool strikethrough = dimstyle->Font().IsStrikethrough(); - m_current_font = &style_font; + this->SetCurrentFont(&style_font); m_current_props.SetColor(color); m_current_props.SetHeight(height); m_current_props.SetStackScale(stackscale); @@ -866,7 +866,7 @@ ON_TextRunBuilder::ON_TextRunBuilder( m_current_props.SetStrikethrough(strikethrough); m_current_run.Init( - m_current_font, + CurrentFont(), height, stackscale, color, @@ -882,13 +882,13 @@ void ON_TextRunBuilder::InitBuilder(const ON_Font* default_font) default_font = &ON_Font::Default; if (nullptr == default_font) return; - m_current_font = default_font; // copy of default_font + this->SetCurrentFont(default_font); // copy of default_font m_in_run = 0; m_level = 0; m_font_table_level = 10000; m_runs = ON_TextRunArray::EmptyArray; - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); // Array for accumulating text codepoints m_current_codepoints.Empty(); @@ -903,7 +903,7 @@ void ON_TextRunBuilder::AppendCurrentRun() { m_runs.AppendRun(run); } - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } } @@ -913,7 +913,7 @@ bool ON_TextRunBuilder::AppendCodePoint(ON__UINT32 codept) if (m_current_codepoints.Count() == 0 && m_current_run.IsStacked() != ON_TextRun::Stacked::kStacked) { // First codepoint in a run after format changes - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } m_current_codepoints.Append(codept); @@ -1019,10 +1019,10 @@ void ON_TextRunBuilder::FinishCurrentRun() // Text string is already stored in m_codepoints // Find or make a managed font like the current one // and store that pointer on the run - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); - const ON_Font* pManagedFont = m_current_font->ManagedFont(); + const ON_Font* pManagedFont = this->CurrentFont()->ManagedFont(); if (nullptr != pManagedFont) { @@ -1115,12 +1115,29 @@ void ON_TextRunBuilder::FinishFontDef() if (!IsValidFontName(rtf_fontname_string)) { ON_ERROR("Invalid font name found in rtf string"); - rtf_fontname_string = L"Arial"; + rtf_fontname_string = ON_Font::Default.RichTextFontName(); + if (rtf_fontname_string.IsEmpty()) + rtf_fontname_string = L"Arial"; } - const ON_Font* managed_font = ON_Font::GetManagedFont(rtf_fontname_string); + // Dale Lear - March 2021 - RH-62974 + // There are multiple places in the text run, rich text parsing, and Rhino UI code + // that need find a font from rich text properties. + // They must all do it the same way. If for some reason, you must change this call, + // please take the time to create a single function that replaces this call, search + // for all the places ON_Font::FontFromRichTextProperties is called in + // src4/rhino/... + // opennurbs_textrun.cpp + // opennurbs_textiterator.cpp + // and replace every call to ON_Font::FontFromRichTextProperties with a call to + // your new function. + // + // This needs to be the regular face of the rich text quartet. + const ON_Font* managed_font = ON_Font::FontFromRichTextProperties(rtf_fontname_string,false,false,false,false); + if (nullptr == managed_font) managed_font = &ON_Font::Default; + rtf_fontname_string = managed_font->RichTextFontName(); ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); @@ -1135,12 +1152,12 @@ void ON_TextRunBuilder::FinishFontDef() { if (m_font_stack.Count() > 0 && m_prop_stack.Count() > 0) { - m_current_font = *m_font_stack.Last(); // pop + this->SetCurrentFont(*m_font_stack.Last()); // pop m_font_stack.Remove(); m_current_props = *m_prop_stack.Last(); // pop m_prop_stack.Remove(); } - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } SetReadingFontDefinition(false); @@ -1159,10 +1176,10 @@ void ON_TextRunBuilder::GroupBegin() // { m_level++; // m_current_font starts out as the value from the text object - m_font_stack.Append(m_current_font); // push prev current font + m_font_stack.Append(this->CurrentFont()); // push prev current font m_prop_stack.Append(m_current_props); // push prev current height and color - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } @@ -1179,12 +1196,12 @@ void ON_TextRunBuilder::GroupEnd() // '}' // Pop font and set up for next run if (m_font_stack.Count() > 0 && m_prop_stack.Count() > 0) { - m_current_font = *m_font_stack.Last(); // pop + this->SetCurrentFont(*m_font_stack.Last()); // pop m_font_stack.Remove(); m_current_props = *m_prop_stack.Last(); // pop m_prop_stack.Remove(); } - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); if (m_level <= m_font_table_level) @@ -1202,7 +1219,7 @@ void ON_TextRunBuilder::RunBegin() // like { with no pushing properties } FinishCurrentRun(); - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } @@ -1217,7 +1234,7 @@ void ON_TextRunBuilder::RunEnd() // like '}' with no popping properties } FinishCurrentRun(); - m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); if (m_level <= m_font_table_level) @@ -1232,8 +1249,7 @@ void ON_TextRunBuilder::FlushText(size_t count, ON__UINT32* cp) m_current_run.SetUnicodeString(count, cp); if(ReadingFontDefinition()) { - // String is a font facename. Make a font with that facename - // and a font definition run + // String is a rich text facename. Make a font from that rich text facename and a font definition run m_current_run.SetType(ON_TextRun::RunType::kFontdef); ON_wString str; @@ -1242,9 +1258,11 @@ void ON_TextRunBuilder::FlushText(size_t count, ON__UINT32* cp) { str.Remove(L';'); // facename delimiter from rtf m_current_run.SetType(ON_TextRun::RunType::kFontdef); - const ON_Font* pManagedFont = ON_Font::GetManagedFont(str); - if (nullptr != pManagedFont) - m_current_font = pManagedFont; + // This is a face name in the rich text font definition table. + // We do not have an bold/italic/underline/strikethrough attributes to apply at this stage. + const ON_Font* rtf_fonttbl_font = ON_Font::FontFromRichTextProperties(str,false,false,false,false); + if (nullptr != rtf_fonttbl_font) + this->SetCurrentFont(rtf_fonttbl_font); } } else @@ -1309,56 +1327,48 @@ static const ON_Font* Internal_UpdateManagedFont( const ON_Font* new_managed_font = nullptr; - if (false == ON_wString::EqualOrdinal(local_rtf_name, ON_Font::RichTextFontName(current_managed_font, false), true)) + const bool bChangeFont = ON_wString::EqualOrdinal(local_rtf_name, ON_Font::RichTextFontName(current_managed_font, false), true) + ? false // rich text face name did not change + : true // rich text face name changed. + ; + const bool bChangeBold = (rtf_bBold?1:0) != (current_managed_font->IsBoldInQuartet()?1:0); + const bool bChangeItalic = (rtf_bItalic ? 1 : 0) != (current_managed_font->IsItalicInQuartet() ? 1 : 0); + const bool bChangeUnderlined = (rtf_bUnderlined?1:0) != (current_managed_font->IsUnderlined()?1:0); + const bool bChangeStrikethrough = (rtf_bStrikethrough?1:0) != (current_managed_font->IsStrikethrough()?1:0); + + if (bChangeFont || bChangeBold || bChangeItalic) { - // changing rich text font name - get everything at once - new_managed_font = ON_Font::ManagedFontFromRichTextProperties( + // Dale Lear - March 2021 - RH-62974 + // There are multiple places in the text run, rich text parsing, and Rhino UI code + // that need find a font from rich text properties. + // They must all do it the same way. If for some reason, you must change this call, + // please take the time to create a single function that replaces this call, search + // for all the places ON_Font::FontFromRichTextProperties is called in + // src4/rhino/... + // opennurbs_textrun.cpp + // opennurbs_textiterator.cpp + // and replace every call to ON_Font::FontFromRichTextProperties with a call to + // your new function. + // + // + // Changing rich text quartet or member in some way must always + // be done using ON_Font::FontFromRichTextProperties(). + // Otherwise this code will not work right for missing fonts. + new_managed_font = ON_Font::FontFromRichTextProperties( local_rtf_name, rtf_bBold, rtf_bItalic, rtf_bUnderlined, rtf_bStrikethrough ); - if (nullptr != new_managed_font) - return new_managed_font; } - - // same rich text font name - const bool bCurrentIsBold = current_managed_font->IsBoldInQuartet(); - const bool bChangeBold = (rtf_bBold != bCurrentIsBold); - const bool bChangeItalic = (rtf_bItalic != current_managed_font->IsItalic()); - const bool bChangeUnderlined = (rtf_bUnderlined != current_managed_font->IsUnderlined()); - const bool bChangeStrikethrough = (rtf_bStrikethrough != current_managed_font->IsStrikethrough()); - - if (false == bChangeBold && false == bChangeItalic) + else if (bChangeUnderlined || bChangeStrikethrough) { - if (bChangeUnderlined || bChangeStrikethrough) - { - ON_Font f(*current_managed_font); - f.SetUnderlined(rtf_bUnderlined); - f.SetStrikethrough(rtf_bStrikethrough); - new_managed_font = f.ManagedFont(); - } - } - else - { - // changing bold or italic - const ON_Font* fontBoldItalic = current_managed_font; - ON_FontFaceQuartet q = current_managed_font->InstalledFontQuartet(); - fontBoldItalic = q.Face(rtf_bBold, rtf_bItalic); - if (nullptr == fontBoldItalic && bChangeBold && bChangeItalic) - fontBoldItalic = q.Face(bCurrentIsBold, rtf_bItalic); - if (nullptr == fontBoldItalic) - fontBoldItalic = current_managed_font; - if (rtf_bUnderlined || rtf_bStrikethrough) - { - ON_Font f(*fontBoldItalic); - f.SetUnderlined(rtf_bUnderlined); - f.SetStrikethrough(rtf_bStrikethrough); - new_managed_font = f.ManagedFont(); - } - else - new_managed_font = fontBoldItalic->ManagedFont(); + // The same rich text quartet member with different underline/strikethrough rendering effects. + ON_Font f(*current_managed_font); + f.SetUnderlined(rtf_bUnderlined); + f.SetStrikethrough(rtf_bStrikethrough); + new_managed_font = f.ManagedFont(); } if (nullptr != new_managed_font) @@ -1391,8 +1401,10 @@ void ON_TextRunBuilder::FontTag(const wchar_t* value) // Set current font to font corresponding to font_index //const ON_Font* pManagedFont = FindFontInMap(nval); - const ON_Font* pManagedFont = (nullptr == m_current_font) - ? &ON_Font::Default : m_current_font->ManagedFont(); + const ON_Font* pManagedFont = (nullptr == this->CurrentFont()) + ? &ON_Font::Default + : this->CurrentFont()->ManagedFont() + ; ON_wString rft_font_name = FaceNameFromMap(nval); rft_font_name.TrimLeftAndRight(); @@ -1412,7 +1424,7 @@ void ON_TextRunBuilder::FontTag(const wchar_t* value) if (nullptr != pManagedFont) { - m_current_font = pManagedFont; + this->SetCurrentFont(pManagedFont); // Use the rtf settings to update m_current_props // to because the current computer may not have an exact font match. m_current_props.SetBold(rtf_bBold); @@ -1482,21 +1494,20 @@ void ON_TextRunBuilder::Bold(const wchar_t* value) on = false; } - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); - if (false == m_current_font->IsManagedFont() || on != m_current_font->IsBoldInQuartet()) + const ON_Font* current_font = this->CurrentFont(); + if (false == current_font->IsManagedFont() || on != current_font->IsBoldInQuartet()) { - const ON_Font* managed_font = m_current_font->ManagedFamilyMemberWithRichTextProperties( + const ON_Font* managed_font = this->CurrentFont()->ManagedFamilyMemberWithRichTextProperties( on, - m_current_font->IsItalic(), - m_current_font->IsUnderlined(), - m_current_font->IsStrikethrough() + current_font->IsItalicInQuartet(), + current_font->IsUnderlined(), + current_font->IsStrikethrough() ); if (nullptr != managed_font) - { - m_current_font = managed_font; - } + this->SetCurrentFont(managed_font); } m_current_props.SetBold(on); } @@ -1512,57 +1523,60 @@ void ON_TextRunBuilder::Italic(const wchar_t* value) on = false; } - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); - if (false == m_current_font->IsManagedFont() || on != m_current_font->IsItalic()) + const ON_Font* current_font = this->CurrentFont(); + if (false == current_font->IsManagedFont() || on != current_font->IsItalicInQuartet()) { - const ON_Font* managed_font = m_current_font->ManagedFamilyMemberWithRichTextProperties( - m_current_font->IsBoldInQuartet(), + const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( + current_font->IsBoldInQuartet(), on, - m_current_font->IsUnderlined(), - m_current_font->IsStrikethrough() + current_font->IsUnderlined(), + current_font->IsStrikethrough() ); if (nullptr != managed_font) - { - m_current_font = managed_font; - } + this->SetCurrentFont(managed_font); } m_current_props.SetItalic(on); } void ON_TextRunBuilder::UnderlineOn() { - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; - if ( false == m_current_font->IsManagedFont() || false == m_current_font->IsUnderlined() ) + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); + + const ON_Font* current_font = this->CurrentFont(); + if ( false == current_font->IsManagedFont() || false == current_font->IsUnderlined() ) { - const ON_Font* managed_font = m_current_font->ManagedFamilyMemberWithRichTextProperties( - m_current_font->IsBoldInQuartet(), - m_current_font->IsItalic(), + const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( + current_font->IsBoldInQuartet(), + current_font->IsItalicInQuartet(), true, - m_current_font->IsStrikethrough() + current_font->IsStrikethrough() ); if (nullptr != managed_font) - m_current_font = managed_font; + this->SetCurrentFont(managed_font); } m_current_props.SetUnderlined(true); } void ON_TextRunBuilder::UnderlineOff() { - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; - if ( false == m_current_font->IsManagedFont() || m_current_font->IsUnderlined() ) + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); + + const ON_Font* current_font = this->CurrentFont(); + if ( false == current_font->IsManagedFont() || current_font->IsUnderlined() ) { - const ON_Font* managed_font = m_current_font->ManagedFamilyMemberWithRichTextProperties( - m_current_font->IsBoldInQuartet(), - m_current_font->IsItalic(), + const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( + current_font->IsBoldInQuartet(), + current_font->IsItalicInQuartet(), false, - m_current_font->IsStrikethrough() + current_font->IsStrikethrough() ); if (nullptr != managed_font) - m_current_font = managed_font; + this->SetCurrentFont(managed_font); } m_current_props.SetUnderlined(false); } @@ -1577,20 +1591,20 @@ void ON_TextRunBuilder::Strikethrough(const wchar_t* value) else if ('0' == value[0]) on = false; } - if (nullptr == m_current_font) - m_current_font = &ON_Font::Default; - if ( false == m_current_font->IsManagedFont() || on != m_current_font->IsStrikethrough() ) + if (nullptr == this->CurrentFont()) + this->SetCurrentFont(&ON_Font::Default); + + const ON_Font* current_font = this->CurrentFont(); + if ( false == current_font->IsManagedFont() || on != current_font->IsStrikethrough() ) { - const ON_Font* managed_font = m_current_font->ManagedFamilyMemberWithRichTextProperties( - m_current_font->IsBoldInQuartet(), - m_current_font->IsItalic(), - m_current_font->IsUnderlined(), + const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( + current_font->IsBoldInQuartet(), + current_font->IsItalicInQuartet(), + current_font->IsUnderlined(), on ); if (nullptr != managed_font) - { - m_current_font = managed_font; - } + this->SetCurrentFont(managed_font); } m_current_props.SetStrikethrough(on); } @@ -2924,8 +2938,11 @@ bool ON_RtfParser::Parse() // AppendCodePoint() returns false to stop parsing if (m_builder.ReadingFontDefinition() && rtf_code_point == L';') m_builder.FinishFontDef(); - else if (!m_builder.AppendCodePoint(rtf_code_point)) - return true; + else + { + if (!m_builder.AppendCodePoint(rtf_code_point)) + return true; + } break; } } // end of big loop for whole string diff --git a/opennurbs_textiterator.h b/opennurbs_textiterator.h index faed93a0..dc091927 100644 --- a/opennurbs_textiterator.h +++ b/opennurbs_textiterator.h @@ -365,8 +365,26 @@ public: virtual ~ON_TextRunBuilder(); ON_SimpleArray< const ON_Font* > m_font_stack; - const ON_Font* m_current_font = &ON_Font::Default; + const ON_Font* CurrentFont() const + { + // Keep this as a simple one-liner that unconditionally returns m_private_current_font. + return m_private_current_font; + } + + void SetCurrentFont(const ON_Font* font) + { + // Keep this as a simple one-liner that unconditionally sets m_private_current_font. + m_private_current_font = font; + } + +private: + // Please, in order to keep people debugging sane, always use + // CurrentFont() / SetCurrentFont() and never directly + // set m_private_current_font. + const ON_Font* m_private_current_font = &ON_Font::Default; + +public: ON_TextRun m_current_run; ON_TextRunArray& m_runs; ON_TextContent& m_text; diff --git a/opennurbs_textlog.cpp b/opennurbs_textlog.cpp index 3b2e31c1..8ec9d6ce 100644 --- a/opennurbs_textlog.cpp +++ b/opennurbs_textlog.cpp @@ -637,6 +637,11 @@ void ON_TextLog::PrintString( const char* s ) } } +void ON_TextLog::PrintString(ON_String s) +{ + PrintString(static_cast(s)); +} + void ON_TextLog::PrintNewLine() { Print("\n"); @@ -663,6 +668,11 @@ void ON_TextLog::PrintString( const wchar_t* s ) } } +void ON_TextLog::PrintString(ON_wString s) +{ + PrintString(static_cast(s)); +} + void ON_TextLog::PrintRGB(const ON_Color& color) { color.ToText(ON_Color::TextFormat::DecimalRGB, 0, true, *this); diff --git a/opennurbs_textlog.h b/opennurbs_textlog.h index 592977c2..22f00934 100644 --- a/opennurbs_textlog.h +++ b/opennurbs_textlog.h @@ -246,20 +246,24 @@ public: /* Description: - Print an unformatted ASCII string of any length. + Print an unformatted UTF-8 encoded null terminated string. Parameters: - s - [in] nullptr terminated ASCII string. + s - [in] UTF-8 encoded null terminated string. */ void PrintString( const char* s ); + + void PrintString(ON_String s); /* Description: - Print an unformatted UNICODE string of any length. + Print an unformatted UTF-16 or UTF-32 encoded null terminated string. Parameters: - s - [in] nullptr terminated UNICODE string. + s - [in] UTF-16 or UTF-32 encoded null terminated string. */ void PrintString( const wchar_t* s ); + void PrintString(ON_wString s); + /* Description: Print color using the format ON_Color::TextFormat::DecimalRGB. diff --git a/opennurbs_textobject.cpp b/opennurbs_textobject.cpp index 83c53e38..d91fcd71 100644 --- a/opennurbs_textobject.cpp +++ b/opennurbs_textobject.cpp @@ -238,7 +238,27 @@ bool ON_Text::Transform(const ON_Xform& xform, const ON_DimStyle* parent_dimstyl bool ON_Text::Transform(const ON_Xform& xform) { - return (ON_Geometry::Transform(xform) && m_plane.Transform(xform)); + bool rc = ON_Geometry::Transform(xform); + if (rc) + rc = m_plane.Transform(xform); + if (rc) + { + ON_TextContent* text = this->Text(); + if (nullptr != text && text->TextIsWrapped()) + { + double w = text->FormattingRectangleWidth(); + ON_3dVector x = m_plane.xaxis; + if (x.Unitize()) + { + double r = text->TextRotationRadians(); + x.Rotate(r, m_plane.zaxis); + x.Transform(xform); + w *= x.Length(); + text->SetFormattingRectangleWidth(w); + } + } + } + return rc; } // returns the base point and width grip using the current alignments @@ -274,6 +294,7 @@ bool ON_Text::GetTextXform( ON_Xform& text_xform_out )const { + return GetTextXform(nullptr, vp, dimstyle, dimscale, text_xform_out); } @@ -284,6 +305,26 @@ bool ON_Text::GetTextXform( double dimscale, ON_Xform& text_xform_out ) const +{ + ON_3dVector view_x = nullptr == vp ? ON_3dVector::XAxis : vp->CameraX(); + ON_3dVector view_y = nullptr == vp ? ON_3dVector::YAxis : vp->CameraY(); + ON_3dVector view_z = nullptr == vp ? ON_3dVector::ZAxis : vp->CameraZ(); + ON::view_projection projection = vp ? vp->Projection() : ON::view_projection::parallel_view; + bool bDrawForward = dimstyle == nullptr ? false : dimstyle->DrawForward(); + return GetTextXform(model_xform, view_x, view_y, view_z, projection, bDrawForward, dimstyle, dimscale, text_xform_out); +} + +bool ON_Text::GetTextXform( + const ON_Xform* model_xform, + const ON_3dVector view_x, + const ON_3dVector view_y, + const ON_3dVector view_z, + ON::view_projection projection, + bool bDrawForward, + const ON_DimStyle* dimstyle, + double dimscale, + ON_Xform& text_xform_out +) const { if (nullptr == dimstyle) return false; @@ -307,9 +348,6 @@ bool ON_Text::GetTextXform( const ON_Plane& textobjectplane = Plane(); wcs2obj_xf.Rotation(ON_Plane::World_xy, textobjectplane); // Rotate text from starting text plane (wcs) to object plane ON_Xform rotation_xf(ON_Xform::IdentityTransformation); - ON_3dVector view_x = nullptr == vp ? ON_3dVector::XAxis : vp->CameraX(); - ON_3dVector view_y = nullptr == vp ? ON_3dVector::YAxis : vp->CameraY(); - ON_3dVector view_z = nullptr == vp ? ON_3dVector::ZAxis : vp->CameraZ(); if ( ON::TextOrientation::InView == dimstyle->TextOrientation() ) // Draw text horizontal and flat to the screen { @@ -336,7 +374,7 @@ bool ON_Text::GetTextXform( rotation_xf.Rotation(textrotation, ON_3dVector::ZAxis, ON_3dPoint::Origin); // Text rotation //ON_Xform textcenter_xf(ON_Xform::IdentityTransformation); - if (dimstyle->DrawForward()) + if (bDrawForward) { // Check if the text is right-reading by comparing // text plane x and y, rotated by text rotation angle, @@ -365,7 +403,7 @@ bool ON_Text::GetTextXform( bool flip_x = false; bool flip_y = false; - const double fliptol = (nullptr != vp && vp->Projection() == ON::view_projection::perspective_view) ? 0.0 : cos(80.001 * ON_DEGREES_TO_RADIANS); + const double fliptol = (projection == ON::view_projection::perspective_view) ? 0.0 : cos(80.001 * ON_DEGREES_TO_RADIANS); CalcTextFlip( text_xdir, text_ydir, text_zdir, view_x, view_y, view_z, diff --git a/opennurbs_textobject.h b/opennurbs_textobject.h index 4d6ea539..4b8847b4 100644 --- a/opennurbs_textobject.h +++ b/opennurbs_textobject.h @@ -144,6 +144,19 @@ private: const class ON_3dmAnnotationContext* annotation_context, const class ON_OBSOLETE_V5_TextObject& V5_text_object ); + public: + bool GetTextXform( + const ON_Xform* model_xform, + const ON_3dVector view_x, + const ON_3dVector view_y, + const ON_3dVector view_z, + ON::view_projection projection, + bool bDrawForward, + const ON_DimStyle* dimstyle, + double dimscale, + ON_Xform& text_xform_out + ) const; + }; #endif diff --git a/opennurbs_textrun.cpp b/opennurbs_textrun.cpp index dbc2905d..92ca47ef 100644 --- a/opennurbs_textrun.cpp +++ b/opennurbs_textrun.cpp @@ -540,13 +540,24 @@ void ON_TextRun::Init( managed_font = &ON_Font::Default; if ( false == managed_font->IsManagedFont() - || managed_font->IsBold() != bold - || managed_font->IsItalic() != italic + || managed_font->IsBoldInQuartet() != bold // NEVER call IsBold() - it doesn't work with light, heavy, black, ... fonts! + || managed_font->IsItalicInQuartet() != italic || managed_font->IsUnderlined() != underlined || managed_font->IsStrikethrough() != strikethrough ) { - managed_font = ON_Font::ManagedFontFromRichTextProperties( + // Dale Lear - March 2021 - RH-62974 + // There are multiple places in the text run, rich text parsing, and Rhino UI code + // that need find a font from rich text properties. + // They must all do it the same way. If for some reason, you must change this call, + // please take the time to create a single function that replaces this call, search + // for all the places ON_Font::FontFromRichTextProperties is called in + // src4/rhino/... + // opennurbs_textrun.cpp + // opennurbs_textiterator.cpp + // and replace every call to ON_Font::FontFromRichTextProperties with a call to + // your new function. + managed_font = ON_Font::FontFromRichTextProperties( ON_Font::RichTextFontName(managed_font, true), bold, italic, diff --git a/opennurbs_textrun.h b/opennurbs_textrun.h index 683da4f3..ef429565 100644 --- a/opennurbs_textrun.h +++ b/opennurbs_textrun.h @@ -35,7 +35,7 @@ public: class ON_TextRun* m_top_run = nullptr; class ON_TextRun* m_bottom_run = nullptr; const ON_TextRun* m_parent_run = nullptr; - wchar_t m_separator = ON_wString::Slash; + wchar_t m_separator = (wchar_t)ON_UnicodeCodePoint::ON_Slash; enum class StackStyle : unsigned char { diff --git a/opennurbs_unicode.cpp b/opennurbs_unicode.cpp index 3eddc7ef..2deebfb1 100644 --- a/opennurbs_unicode.cpp +++ b/opennurbs_unicode.cpp @@ -2634,6 +2634,14 @@ int ON_ConvertUTF32ToWideChar( return rc; } +const ON_wString ON_wString::FromUnicodeCodePoint( + ON__UINT32 code_point +) +{ + return ON_wString::FromUnicodeCodePoints(&code_point, 1, ON_UnicodeCodePoint::ON_ReplacementCharacter); +} + + const ON_wString ON_wString::FromUnicodeCodePoints( const ON__UINT32* code_points, int code_point_count, @@ -3079,3 +3087,202 @@ int ON_ConvertMSMBCPToWideChar( #endif } + + + +unsigned ON_UnicodeSuperscriptFromCodePoint( + unsigned cp, + unsigned no_superscript_cp +) +{ + if (cp >= '0' && cp <= '9') + { + static const unsigned digit_cp[10] + { + 0x2070, + 0x00B9, + 0x00B2, + 0x00B3, + 0x2074, + 0x2075, + 0x2076, + 0x2077, + 0x2078, + 0x2079 + }; + return digit_cp[cp - '0']; + } + else if (cp >= 'a' && cp <= 'z') + { + // a-z + static const unsigned atoz_cp[26] + { + 0x1D43, // a + 0x1d47, // b + 0x1d9c, // c + 0x1d48, // d + 0x1d49, // e + 0x1da0, // f + 0x1d4d, // g + 0x20b0, // h + 0x2071, // i + 0x02b2, // j + 0x1d4f, // k + 0x02e1, // l + 0x1d40, // m + 0x207f, // n + 0x1d52, // o + 0x1d56, // p + 0, // q NONE AVAILABLE + 0x02b3, // r + 0x02e2, // s + 0x1d57, // t + 0x1d58, // u + 0x1d5b, // v + 0x02b7, // w + 0x02e3, // x + 0x02b8, // y + 0x1dbb // z + }; + const unsigned sup_cp = atoz_cp[cp - 'a']; + if (0 != sup_cp) + return sup_cp; + } + else if (cp >= 'A' && cp <= 'Z') + { + // a-z + static const unsigned atoz_cp[26] + { + 0x1DC2, // A + 0x1D2D, // B + 0, // C NOT AVAILABLE + 0x1D30, // D + 0x1D31, // E + 0, // F NOT AVAILABLE + 0x1D33, // G + 0x1D34, // H + 0x1D35, // I + 0x1D36, // J + 0x1D37, // K + 0x1D38, // L + 0x1D39, // M + 0x1D3A, // N + 0x1D3C, // O + 0x1D3E, // P + 0, // Q NOT AVIALABLE + 0x1D3F, // R + 0, // S NOT AVAILABLE + 0x1D40, // T + 0x2C7D, // V + 0x1D42, // W + 0, // X NOT AVAILABLE + 0, // Y NOT AVAILABLE + 0 // Z NOT AVAILABLE + }; + const unsigned sup_cp = atoz_cp[cp - 'a']; + if (0 != sup_cp) + return sup_cp; + } + else + { + switch (cp) + { + case '+': + return 0x207A; // + + break; + case '-': + return 0x207B; // - + break; + case '=': + return 0x207C; // = + break; + case '(': + return 0x207C; // = + break; + case ')': + return 0x207E; // ) + break; + } + } + + // either cp is already a superscript or none is avilable. + return no_superscript_cp; +} + + +unsigned ON_UnicodeSubscriptFromDigit(unsigned decimal_digit) +{ + if (decimal_digit >= 0 && decimal_digit <= 9) + return 0x2080U + decimal_digit; + return 0; +} + +unsigned ON_UnicodeSuperscriptFromDigit(unsigned decimal_digit) +{ + switch (decimal_digit) + { + case 1: + return 0x00B9U; + break; + case 2: + return 0x00B2U; + break; + case 3: + return 0x00B3U; + break; + default: + if (decimal_digit >= 4 && decimal_digit <= 9) + return (0x2070U + decimal_digit); + break; + } + return 0; +} + +unsigned ON_UnicodeSubcriptFromCodePoint( + unsigned cp, + unsigned no_subscript_cp +) +{ + if (cp >= '0' && cp <= '9') + { + static const unsigned digit_cp[10] + { + 0x2080, + 0x2081, + 0x2082, + 0x2083, + 0x2084, + 0x2085, + 0x2086, + 0x2087, + 0x2088, + 0x2089 + }; + return digit_cp[cp - '0']; + } + else + { + switch (cp) + { + case '+': + return 0x208A; // + + break; + case '-': + return 0x208B; // - + break; + case '=': + return 0x208C; // = + break; + case '(': + return 0x208C; // = + break; + case ')': + return 0x208E; // ) + break; + } + } + + // either cp is already a subscript or none is avilable. + return cp; +} + diff --git a/opennurbs_unicode.h b/opennurbs_unicode.h index 54b67600..30db5951 100644 --- a/opennurbs_unicode.h +++ b/opennurbs_unicode.h @@ -32,113 +32,325 @@ enum ON_UnicodeEncoding ON_UTF_32LE // UTF-32 little endian CPU byte order }; +// UTF-8 encodings: +// The UTF-8 encoding for codepoint values from 0 to 127 is a single single byte (char). +// The UTF-8 encoding for codepoint values >= 128 require multiple bytes. +// UTF-16 encodings: +// The UTF-16 encoding of every codepoint in this enum except Wastebasket is a single word (unsigned short). + + + +/// +/// Unicode code point values for that are hard to include in code or +/// are useful for testing encoding and glyph rendering. +/// Code points >= U+0080 require UTF-8 multiple byte encodings. +/// Code points >= U+10000 require UTF-16 surrogate pair encodings. +/// enum ON_UnicodeCodePoint { - // UTF-8 encodings: - // The UTF-8 encoding for codepoint values from 0 to 127 is a single single byte (char). - // The UTF-8 encoding for codepoint values >= 128 require multiple bytes. - // UTF-16 encodings: - // The UTF-16 encoding of every codepoint in this enum except Wastebasket is a single word (unsigned short). + /// nullptr control U+0000 + ON_NullCodePoint = 0x00, - ON_NullCodePoint = 0x00, // nullptr control U+0000 (decimal 0) - ON_Backspace = 0x08, // BACKSPACE control U+0008 (decimal 8) - ON_Tab = 0x09, // CHARACTER TABULATION control U+0009 (decimal 9) - ON_LineFeed = 0x0A, // LINE FEED control U+000A (decimal 10) - ON_VerticalTab = 0x0B, // LINE TABULATION control U+000B (decimal 11) - ON_FormFeed = 0x0C, // FORM FEED control U+000C (decimal 12) - ON_CarriageReturn = 0x0D, // CARRIAGE RETURN control U+000D (decimal 13) - ON_Escape = 0x1B, // CARRIAGE RETURN control U+001B (decimal 27) - ON_Space = 0x20, // SPACE U+0020 (decimal 32) - ON_HyphenMinus = 0x2D, // HYPHEN-MINUS U+002D (decimal 45) - ON_Slash = 0x2F, // SOLIDUS U+002F (decimal 47) - ON_Backslash = 0x5C, // REVERSE SOLIDUS U+005C (decimal 92)ere - ON_Underscore = 0x5F, // Unicode LOW LINE U+005F - ON_Pipe = 0x7C, // VERTICAL LINE U+007C (decimal 124) - ON_Tilde = 0x7E, // TILDE U+007E (decimal 126) - ON_Period = 0x2E, // PERIOD U+002E (decimal 46) - ON_Comma = 0x2C, // COMMA U+002C (decimal 44) + /// BACKSPACE control U+0008 + ON_Backspace = 0x08, - // - // NOTE: UTF-8 encodings of the codepoint values below this comment require multiple bytes. - // - ON_NextLine = 0x0085, // NEXT LINE (NEL) U+0085 - ON_NoBreakSpace = 0x00A0, // NO-BREAK SPACE (NBSP) - ON_NarrowNoBreakSpace = 0x202F, // NARROW NO-BREAK SPACE (NNBSP) - ON_ZeroWidthSpace = 0x200B, // ZERO WIDTH SPACE (ZWSP) + /// CHARACTER TABULATION control U+0009 + ON_Tab = 0x09, - ////////////////////////////////////////////////////////////// - // - // Annotation symbols - // - ON_RadiusSymbol = 0x0052, // LATIN CAPITAL LETTER R U+0052 (decimal 82) - ON_DegreeSymbol = 0x00B0, // DEGREE SIGN U+00B0 (decimal 176) - ON_PlusMinusSymbol = 0x00B1, // PLUS-MINUS SIGN U+00B1 (decimal 177) - ON_DiameterSymbol = 0x00D8, // LATIN CAPITAL LETTER O WITH STROKE U+00D8 (decimal 216) + /// LINE FEED control U+000A + ON_LineFeed = 0x0A, - ////////////////////////////////////////////////////////////// - // - // Unambiguous format control code points - // - ON_LineSeparator = 0x2028, // LINE SEPARATOR U+2028 unambiguous line separator - ON_ParagraphSeparator = 0x2029, // PARAGRAPH SEPARATOR U+2028 unambiguous paragraph separator + /// LINE TABULATION control U+000B + ON_VerticalTab = 0x0B, - ////////////////////////////////////////////////////////////// - // - // Greek, Cyrillic and CJK glyph code points used for testing purposes. - // - ON_GreekAlpha = 0x03B1, // GREEK SMALL LETTER ALPHA - ON_CyrillicCapitalYu = 0x042E, // CYRILLIC CAPITAL LETTER YU + /// FORM FEED control U+000C + ON_FormFeed = 0x0C, + + /// CARRIAGE RETURN control U+000D + ON_CarriageReturn = 0x0D, + + /// ESCAPE control U+001B + ON_Escape = 0x1B, + + /// SPACE U+0020 + ON_Space = 0x20, + + /// NO-BREAK SPACE (NBSP) U+00A0 + ON_NoBreakSpace = 0x00A0, + + /// OGHAM SPACE MARK U+1680 + ON_OghamSpaceMark = 0x1680, + + /// EN QUAD SPACE U+2000 + ON_EnQuad = 0x2000, + + /// EM QUAD SPACE U+2001 + ON_EmQuad = 0x2001, + + /// EN SPACE U+2002 Also known as a nut. (About 1/2 EM SPACE) + ON_EnSpace = 0x2002, + + /// EM SPACE U+2003 Also known as a mutton. + ON_EmSpace = 0x2003, + + /// 3 per EM SPACE U+2004 (1/3 EM SPACE) + ON_ThreePerEmSpace = 0x2004, + + /// 4 per EM SPACE U+2005 (1/4 EM SPACE) + ON_FourPerEmSpace = 0x2005, + + /// 6 per EM SPACE U+2006 (1/6 EM SPACE) + ON_SixPerEmSpace = 0x2006, + + /// ZERO WIDTH SPACE (ZWSP) U+200B + ON_ZeroWidthSpace = 0x200B, + + /// FIGURE SPACE U+2007 Average digit width. + ON_FigureSpace = 0x2007, + + /// PUNCTUATION SPACE U+2008 + ON_PunctuationSpace = 0x2008, + + /// THIN SPACE U+2009 (1/5 to 1/6 EM SPACE) + ON_ThinSpace = 0x2009, + + /// HAIR SPACE U+200A (Narrower than THIN SPACE) + ON_HairSpace = 0x200A, + + /// MEDIUM MATHEMATICAL SPACE U+2025 (about 2/9 EM SPACE) + ON_MediumMathematicalSpace = 0x205F, + + /// IDEOGRAPHIC SPACE U+3000 The width of ideographic (Chinese, Japanese, Korean) characters. + ON_IdeographicSpace = 0x3000, + + /// zero with non-joiner (ZWNJ) U+200C + ON_ZeroWidthNonJoiner = 0x200C, + + /// zero with joiner (ZWJ) U+200D + ON_ZeroWidthJoiner = 0x200D, + + /// NARROW NO-BREAK SPACE (NNBSP) U+202F + ON_NarrowNoBreakSpace = 0x202F, + + /// QUOTATION MARK U+0022 (") + ON_QuotationMark = 0x22, + + /// NUMBER SIGN U+0023 (#) + ON_NumberSign = 0x23, + + /// PERCENT SIGN U+0025 (%) + ON_PercentSign = 0x25, + + /// AMPERSAND U+0026 (&) + ON_Ampersand = 0x26, + + /// APOSTROPHE U+0027 (') + ON_Apostrophe = 0x27, + + /// COMMA U+002C (,) + ON_Comma = 0x2C, + + /// HYPHEN-MINUS U+002D (-) + ON_HyphenMinus = 0x2D, + + /// UNAMBIGUOUS HYPHEN U+2010 (‐) + ON_UnambiguousHyphen = 0x2010, + + /// NON-BREAKING HYPHEN U+2011 + ON_NoBreakHyphen = 0x2011, + + /// SMALL HYPHEN U+FE63 (﹣) + ON_SmallHyphen = 0xFE63, + + /// UNAMBIGUOUS MINUS U+2212 (−) + ON_UnambiguousMinus = 0x2212, + + /// UNAMBIGUOUS MINUS U+2012 (‒) + ON_FigureDash = 0x2012, + + /// EN DASH U+2013 (–) + ON_EnDash = 0x2013, + + /// EM DASH U+2014 (—) + ON_EmDash = 0x2014, + + /// PERIOD U+002E (decimal 46) (.) + ON_Period = 0x2E, + + /// SOLIDUS U+002F (/) + ON_Slash = 0x2F, + + /// FRACTION SLASH U+2044 (⁄) + ON_FractionSlash = 0x2044, + + /// DIVISION SLASH U+2215 (∕) + ON_DivisionSlash = 0x2215, + + /// MATHEMATICAL RISING DIAGONAL U+27CB (⟋) + ON_MathimaticalSlash = 0x27CB, + + /// COLON U+003A (:) + ON_Colon = 0x3A, + + /// SEMICOLON U+003B (;) + ON_Semicolon = 0x3B, + + /// LESS-THAN SIGN U+003C (<) + ON_LessThanSign = 0x3C, + + /// GREATER-THAN SIGN U+003E (>) + ON_GreaterThanSign = 0x3E, + + /// REVERSE SOLIDUS U+005C (\) + ON_Backslash = 0x5C, + + /// // Unicode LOW LINE U+005F (_) + ON_Underscore = 0x5F, + + /// VERTICAL LINE U+007C (|) + ON_Pipe = 0x7C, + + /// TILDE U+007E (~) + ON_Tilde = 0x7E, + + /// NEXT LINE (NEL) control U+0085 + ON_NextLine = 0x0085, + + /// LATIN CAPITAL LETTER R U+0052 (decimal 82) (Rhino annotation radius symbol) + ON_RadiusSymbol = 0x0052, + + /// DEGREE SIGN U+00B0 (X°) (Rhino annotation degree symbol) + ON_DegreeSymbol = 0x00B0, + + /// PLUS-MINUS SIGN U+00B1 (±) (Rhino annotation plus/minus symbol) + ON_PlusMinusSymbol = 0x00B1, + + /// SUPERSCRIPT TWO U+00B2 (X²) (Rhino annotation length squared symbol) + ON_Superscript2 = 0x00B2, + + /// SUPERSCRIPT THREE U+00B3 (X³) (Rhino annotation length cubed symbol) + ON_Superscript3 = 0x00B3, + + /// LATIN CAPITAL LETTER O WITH STROKE U+00D8 (Ø) (Rhino annotation diametersymbol) + ON_DiameterSymbol = 0x00D8, + + /// LINE SEPARATOR U+2028 unambiguous line separator + ON_LineSeparator = 0x2028, + + /// PARAGRAPH SEPARATOR U+2028 unambiguous paragraph separator + ON_ParagraphSeparator = 0x2029, + + /// GREEK CAPITAL LETTER ALPHA U+0391 (Α) + ON_GreekCapitalAlpha = 0x0391, + + /// GREEK SMALL LETTER ALPHA U+03B1 (α) + ON_GreekAlpha = 0x03B1, + + /// GREEK CAPITAL LETTER SIGMA U+03A3 (Σ) + ON_GreekCapitalSigma = 0x03A3, + + /// GREEK SMALL LETTER SIGMA U+03C3 (σ) + ON_GreekSigma = 0x03C3, + + /// GREEK CAPITAL LETTER OMEGA U+03A9 (Ω) + ON_GreekCapitalOmega = 0x03A9, + + /// GREEK SMALL LETTER OMEGA U+03C9 (ω) + ON_GreekOmega = 0x03C9, + + /// CYRILLIC CAPITAL LETTER YU U+042E (Ю) (Used in Cyrillic code point tests) + ON_CyrillicCapitalYu = 0x042E, + + /// Simplified Chinese logogram for tree U+6881 (梁) (Used in CJK code point tests) ON_SimplifiedChineseTree = 0x6881, + + /// Traditional Chinese logogram for tree U+6A39 (樹) (Used in CJK code point tests) ON_TraditionalChineseTree = 0x6A39, + + /// Japanese logogram for rhinoceros U+7280 (犀) (Used in CJK code point tests) ON_JapaneseRhinoceros = 0x7280, + + /// Japanese logogram for tree U+6728 (木) (Used in CJK code point tests) ON_JapaneseTree = 0x6728, + + /// Korean HAN U+D55C (한) (Used in CJK code point tests) ON_KoreanHan = 0xD55C, + + /// Korean JEONG U+C815 (정) (Used in CJK code point tests) ON_KoreanJeong = 0xC815, - ////////////////////////////////////////////////////////////// - // - // Currency symbols - // - ON_DollarSign = 0x0024, // DOLLAR SIGN U+0024 - ON_CentSign = 0x00A2, // CENT SIGN U+00A2 - ON_PoundSign = 0x00A3, // POUND SIGN U+00A3 - ON_CurrencySign = 0x00A4, // CURRENCY SIGN U+00A4 - ON_YenSign = 0x00A5, // YEN SIGN U+00A5 (Chinese yuan, Japanese yen) - ON_EuroSign = 0x20AC, // EURO SIGN U+20AC - ON_PesoSign = 0x20B1, // PESO SIGN U+20B1 - ON_RubleSign = 0x20BD, // RUBLE SIGN U+20BD + /// DOLLAR SIGN U+0024 ($) + ON_DollarSign = 0x0024, - ////////////////////////////////////////////////////////////// - // - // RECYCLING SYMBOL is useful for testing symbol font substitution - // - ON_RecyclingSymbol = 0x2672, // UNIVERSAL RECYCLING SYMBOL U+2672 (decimal 9842) - ON_BlackRecyclingSymbol = 0x267B, // BLACK UNIVERSAL RECYCLING SYMBOL U+267B (decimal 9851) + /// CENT SIGN U+00A2 (¢) + ON_CentSign = 0x00A2, - ////////////////////////////////////////////////////////////// - // - // REPLACEMENT CHARACTER is the conventional glpyh used - // to mark locations where UTF encodings contain invalid - // information. - // - ON_ReplacementCharacter = 0xFFFD, // REPLACEMENT CHARACTER U+FFFD (decimal 65533) + /// POUND SIGN U+00A3 (£) + ON_PoundSign = 0x00A3, - ////////////////////////////////////////////////////////////// - // - // WASTEBASKET (Good value for testing UTF-16 surrogte pair handling) - // - // wchar_t sWastebasket[] = {0xD83D,0xDDD1,0}; // correct on Windows. (Windows wchar_t strings are UTF-16 encoded). - // wchar_t sWastebasket[] = {0x1F5D1,0}; // correct on OS X (OS X wchar_t strings are UTF-32 encoded). - // - // WASTEBASKET UTF-8 encodeing = (0xF0, 0x9F, 0x97, 0x91) - // WASTEBASKET UTF-16 encodeing = ( 0xD83D, 0xDDD1 ) (surrogate pair) - ON_Wastebasket = 0x1F5D1, // WASTEBASKET U+1F5D1 (decimal 128465) + /// CURRENCY SIGN U+00A4 (¤) + ON_CurrencySign = 0x00A4, - ////////////////////////////////////////////////////////////// - // - // Valid codepoint values are <= 0x10FFFF - // See ON_IsValidUnicodeCodepoint() for additional restrictions. - // + /// YEN SIGN U+00A5 (Chinese yuan, Japanese yen) (¥) + ON_YenSign = 0x00A5, + + /// EURO SIGN U+20AC (€) + ON_EuroSign = 0x20AC, + + /// PESO SIGN U+20B1 (₱) + ON_PesoSign = 0x20B1, + + /// RUBLE SIGN U+20BD (₽) + ON_RubleSign = 0x20BD, + + /// + /// UNIVERSAL RECYCLING SYMBOL U+2672 (♲) + /// This is a good cold point for testing glyph substitution. + /// + ON_RecyclingSymbol = 0x2672, + + + /// + /// BLACK UNIVERSAL RECYCLING SYMBOL U+267B (♻) + /// This is a good cold point for testing glyph substitution. + /// + ON_BlackRecyclingSymbol = 0x267B, + + /// + /// BYTE ORDER MARK (BOM) U+FEFF + /// U+FEFF is used at the beginning of UTF encoded text to indicate the + /// byte order being used. It is valid for UTF-8 encoded text to begin + /// with the UTF-8 encoding of U+FEFF (0xEF,0xBB,0xBF). + /// This sometimes used to mark a char string as UTF-8 encoded. + /// and also occures when UTF-16 and UTF-32 encoded text with a byte + /// order mark is converted to UTF-8 encoded text. + /// + ON_ByteOrderMark = 0xFEFF, + + /// + /// REPLACEMENT CHARACTER U+FFFD (�) + /// By convention, U+FFFD is used to mark string elements where + /// an invalid UTF code point encoding was encountered. + /// + ON_ReplacementCharacter = 0xFFFD, + + /// + /// WASTEBASKET U+1F5D1 (🗑) + /// The wastebasket is a good code point to test glyph rendering and UTF-16 surrogate pair encodings. + /// UTF-8 encodeing = (0xF0, 0x9F, 0x97, 0x91) + /// UTF-16 encodeing = ( 0xD83D, 0xDDD1 ) (UTF-16surrogate pair) + /// + ON_Wastebasket = 0x1F5D1, + + /// + /// The maximum valid unicode code point value is 0x10FFFF. + /// + ON_MaximumCodePoint = 0x10FFFF, + + /// + /// The maximum valid unicode code point value is 0x10FFFF. + /// See ON_IsValidUnicodeCodepoint() for additional restrictions. + /// ON_InvalidCodePoint = 0x110000 }; @@ -3509,5 +3721,78 @@ bool ON_IsUnicodeControlCodePoint( ON__UINT32 code_point, bool bNullReturnValue ); + +/// +/// When possible, converts a code point to a superscript code point. Note that many common fonts +/// typecast the Unicode digit superscripts as vulgar fraction numerators rather than a proper superscript. +/// +/// +/// Unicode code point for which a superscript is desired. +/// +/// +/// When in doubt, pass cp. +/// +/// +/// If the code point cp has a Unicode superscript, the code point of the superscript is returned. +/// Otherwise no_superscript_cp is returned. +/// +ON_DECL +unsigned ON_UnicodeSuperscriptFromCodePoint( + unsigned cp, + unsigned no_superscript_cp +); + +/// +/// Get the Unicode code point for a decimal digit superscript. +/// +/// +/// 0 <= decimal_digit <= 9 +/// +/// +/// If 0 <= decimal_digit <= 9, then the Unicode code point for the superscript digit is returned. +/// Otherwise 0 is returned. +/// +ON_DECL +unsigned ON_UnicodeSuperscriptFromDigit( + unsigned decimal_digit +); + + +/// +/// When possible, converts a code point to a subscript code point. Note that many common fonts +/// typecast the Unicode digit subscripts as vulgar fraction denominators rather than a proper subscript. +/// +/// +/// Unicode code point for which a subscript is desired. +/// +/// +/// When in doubt, pass cp. +/// +/// +/// If the code point cp has a Unicode subscript, the code point of the subscript is returned. +/// Otherwise no_subscript_cp is returned. +/// +ON_DECL +unsigned ON_UnicodeSubcriptFromCodePoint( + unsigned cp, + unsigned no_subscript_cp +); + +/// +/// Get the Unicode code point for a decimal digit subscript. +/// +/// +/// 0 <= decimal_digit <= 9 +/// +/// +/// If 0 <= decimal_digit <= 9, then the Unicode code point for the subscript digit is returned. +/// Otherwise 0 is returned. +/// +ON_DECL +unsigned ON_UnicodeSubscriptFromDigit( + unsigned decimal_digit +); + + #endif #endif diff --git a/opennurbs_win_dwrite.cpp b/opennurbs_win_dwrite.cpp index 24e8ea11..a42c1cd3 100644 --- a/opennurbs_win_dwrite.cpp +++ b/opennurbs_win_dwrite.cpp @@ -102,27 +102,58 @@ IDWriteGdiInterop* ON_IDWrite::GdiInterop() // static bool Internal_GetLocalizeStrings( - const wchar_t* preferedLocale, + const ON_wString preferedLocaleDirty, IDWriteLocalizedStrings* pIDWriteLocalizedStrings, ON_wString& defaultLocaleString, - ON_wString& enusLocaleString + ON_wString& defaultLocale, + ON_wString& enLocaleString, + ON_wString& enLocale ) { defaultLocaleString = ON_wString::EmptyString; - enusLocaleString = ON_wString::EmptyString; + defaultLocale = ON_wString::EmptyString; + enLocaleString = ON_wString::EmptyString; + enLocale = ON_wString::EmptyString; - wchar_t userDefaultLocale[LOCALE_NAME_MAX_LENGTH + 1] = {}; + + // get a clean preferedLocale with reliable storage for the string. + ON_wString preferedLocale(preferedLocaleDirty.Duplicate()); + preferedLocale.TrimLeftAndRight(); if (ON_wString::EqualOrdinal(preferedLocale, L"GetUserDefaultLocaleName", true)) { // Get the default locale for this user. + wchar_t userDefaultLocale[LOCALE_NAME_MAX_LENGTH + 1] = {}; userDefaultLocale[LOCALE_NAME_MAX_LENGTH] = 0; if (0 == ::GetUserDefaultLocaleName(userDefaultLocale, LOCALE_NAME_MAX_LENGTH)) userDefaultLocale[0]; userDefaultLocale[LOCALE_NAME_MAX_LENGTH] = 0; - preferedLocale = userDefaultLocale; + preferedLocale = ON_wString(userDefaultLocale); + preferedLocale.TrimLeftAndRight(); } - const wchar_t* enusLocaleName = L"en-us"; + const wchar_t* localeNames[] = + { + static_cast(preferedLocale), + + // The preferred English dialect should be listed next + L"en-us", // United States + + // Other known English dialects are list below + L"en-au", // Australia + L"en-bz", // Belize + L"en-ca", // Canada + L"en-cb", // Caribbean + L"en-gb", // Great Britain + L"en-in", // India + L"en-ie", // Ireland + L"en-jm", // Jamaica + L"en-nz", // New Zealand + L"en-ph", // Philippines + L"en-tt", // Trinidad + L"en-za", // Southern Africa + }; + + const int localNamesCount = (int)(sizeof(localeNames)/sizeof(localeNames[0])); HRESULT hr; @@ -134,10 +165,10 @@ static bool Internal_GetLocalizeStrings( return ON_wString::EmptyString; UINT32 localeIndex = ON_UNSET_UINT_INDEX; - UINT32 enusLocaleIndex = ON_UNSET_UINT_INDEX; - for (int i = 0; i < 2; i++) + UINT32 enLocaleIndex = ON_UNSET_UINT_INDEX; + for (int i = 0; i < localNamesCount; i++) { - const wchar_t* s = (i > 0) ? enusLocaleName : preferedLocale; + const wchar_t* s = localeNames[i]; if (nullptr == s || 0 == s[0]) continue; BOOL exists = 0; @@ -151,14 +182,20 @@ static bool Internal_GetLocalizeStrings( continue; if (i > 0) { - enusLocaleIndex = idx; + enLocaleIndex = idx; + enLocale = s; } else { + // preferred locale localeIndex = idx; - if (ON_wString::EqualOrdinal(enusLocaleName, -1, s, -1, true)) + if ( + 0 != s[0] && 0 != s[1] && 0 != s[2] + && ON_wString::EqualOrdinal(L"en-", 3, s, 3, true) + ) { - enusLocaleIndex = idx; + // preferred local is the preferred English dialect too. + enLocaleIndex = idx; break; } } @@ -168,10 +205,10 @@ static bool Internal_GetLocalizeStrings( { if (pass > 1) { - if (defaultLocaleString.IsNotEmpty() || enusLocaleString.IsNotEmpty()) + if (defaultLocaleString.IsNotEmpty() || enLocaleString.IsNotEmpty()) break; } - bool bGetLocaleName = false; + UINT32 i0, i1; if (0 == pass) { @@ -183,9 +220,9 @@ static bool Internal_GetLocalizeStrings( else if (1 == pass) { - if (ON_UNSET_UINT_INDEX == enusLocaleIndex || enusLocaleIndex == localeIndex ) + if (ON_UNSET_UINT_INDEX == enLocaleIndex || enLocaleIndex == localeIndex ) continue; - i0 = enusLocaleIndex; + i0 = enLocaleIndex; i1 = i0 + 1; } else @@ -200,7 +237,11 @@ static bool Internal_GetLocalizeStrings( { ON_wString strStringValue; ON_wString strLocaleName; - for (int value = 0; value < (bGetLocaleName?2:1); value++) + if (i == localeIndex) + strLocaleName = preferedLocale; + else if (i == enLocaleIndex) + strLocaleName = enLocale; + for (int value = 0; value < (strLocaleName.IsEmpty()?2:1); value++) { const bool bStringValuePass = (0 == value); UINT32 slen = 0; @@ -243,17 +284,24 @@ static bool Internal_GetLocalizeStrings( if ( pass >= 2 && defaultLocaleString.IsEmpty() ) { defaultLocaleString = strStringValue; + defaultLocale = strLocaleName; } if (i == localeIndex) + { defaultLocaleString = strStringValue; + defaultLocale = strLocaleName; + } - if (i == enusLocaleIndex) - enusLocaleString = strStringValue; + if (i == enLocaleIndex) + { + enLocaleString = strStringValue; + enLocale = strLocaleName; + } if ( defaultLocaleString.IsNotEmpty() - && enusLocaleString.IsNotEmpty() + && enLocaleString.IsNotEmpty() ) { break; @@ -262,11 +310,32 @@ static bool Internal_GetLocalizeStrings( } if (defaultLocaleString.IsEmpty()) - defaultLocaleString = enusLocaleString; + { + defaultLocaleString = enLocaleString; + defaultLocale = enLocale; + } return defaultLocaleString.IsNotEmpty(); } + +static bool Internal_GetLocalizeStrings( + const wchar_t* preferedLocale, + IDWriteLocalizedStrings* pIDWriteLocalizedStrings, + ON_wString& defaultLocaleString, + ON_wString& enLocaleString +) +{ + ON_wString defaultLocale; + ON_wString enusLocale; + return Internal_GetLocalizeStrings( + preferedLocale, + pIDWriteLocalizedStrings, + defaultLocaleString, defaultLocale, + enLocaleString, enusLocale + ); +} + static void Internal_PrintLocalizedNames( const ON_wString stringDescription, const wchar_t* preferedLocale, @@ -2191,4 +2260,203 @@ IDWriteFont* ON_Font::WindowsDWriteFont() const // // +static bool Internal_GetDWriteFamilyName( + const IDWriteFont* dwrite_font, + const ON_wString preferedLocaleDirty, + ON_wString& familyName, + ON_wString& locale +) +{ + familyName = ON_wString::EmptyString; + locale = ON_wString::EmptyString; + if (nullptr == dwrite_font) + return false; + + // The name id are list in decreasing order of reliability. + DWRITE_INFORMATIONAL_STRING_ID name_id[] = + { + /// + /// Family name for the weight-stretch-style model. + /// + DWRITE_INFORMATIONAL_STRING_WEIGHT_STRETCH_STYLE_FAMILY_NAME, + + // Typographic family name preferred by the designer. This enables font designers to group more than four fonts in a single family without losing compatibility with + // GDI. This name is typically only present if it differs from the GDI-compatible family name. + DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_FAMILY_NAMES, + + // GDI-compatible family name. Because GDI allows a maximum of four fonts per family, fonts in the same family may have different GDI-compatible family names + // (e.g., "Arial", "Arial Narrow", "Arial Black"). + DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, + }; + + const size_t name_id_count = sizeof(name_id) / sizeof(name_id[0]); + + ON_wString preferedLocale(preferedLocaleDirty); + preferedLocale.TrimLeftAndRight(); + if (preferedLocale.IsEmpty()) + preferedLocale = ON_wString(L"GetUserDefaultLocaleName"); + + for (size_t i = 0; i < name_id_count; ++i) + { + // iterate possible family names finding the best one to return. + BOOL exists = false; + Microsoft::WRL::ComPtr localizedFamilynames = nullptr; + HRESULT hr = const_cast(dwrite_font)->GetInformationalStrings(name_id[i], &localizedFamilynames, &exists); + if (FAILED(hr)) + break; + + IDWriteLocalizedStrings* familyNames = localizedFamilynames.Get(); + if (nullptr == familyNames) + continue; + + ON_wString defaultName; + ON_wString defaultLocale; + ON_wString enName; + ON_wString enLocale; + if (exists) + { + if (Internal_GetLocalizeStrings( + preferedLocale, + familyNames, + defaultName, + defaultLocale, + enName, + enLocale + )) + { + if (defaultName.IsNotEmpty() && defaultLocale.IsNotEmpty()) + { + familyName = defaultName; + locale = defaultLocale; + break; + } + } + } + } + + return familyName.IsNotEmpty(); +} + +static IDWriteTextFormat* Internal_TextFormat(const IDWriteFont* dwriteFont) +{ + if (nullptr == dwriteFont) + return nullptr; + + ON_wString familyName; + ON_wString locale; + const bool bHaveFamilyName = Internal_GetDWriteFamilyName(dwriteFont,ON_wString::EmptyString,familyName,locale); + if (false == bHaveFamilyName || familyName.IsEmpty() || locale.IsEmpty()) + return nullptr; + + IDWriteFactory* dwiteFactory = ON_IDWrite::Factory(); + if (nullptr == dwiteFactory) + return nullptr; + + DWRITE_FONT_WEIGHT weight = const_cast(dwriteFont)->GetWeight(); + DWRITE_FONT_STYLE style = const_cast(dwriteFont)->GetStyle(); + DWRITE_FONT_STRETCH stretch = const_cast(dwriteFont)->GetStretch(); + + float logical_size_dip = 96.0; // 96 DIP ("device-independent pixel") = one inch + + IDWriteTextFormat* dwriteTextFormat = nullptr; + + HRESULT hr = dwiteFactory->CreateTextFormat( + static_cast(familyName), + nullptr, // use System font collection + weight, style, stretch, + logical_size_dip, + static_cast(locale), + &dwriteTextFormat + ); + + if (SUCCEEDED(hr)) + hr = dwriteTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); + + // Center align (vertically) the text. + if (SUCCEEDED(hr)) + hr = dwriteTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); + + if (SUCCEEDED(hr)) + return dwriteTextFormat; + + return nullptr; +} + + +//static +IDWriteTextLayout* Internal_TextLayout( + ON_wString textString, + const ON_Font* font, + double height +) +{ + if (nullptr == font) + return nullptr; + + if (textString.IsEmpty()) + return nullptr; + + const UINT32 textStringLength = (UINT32)textString.Length(); + + // sanity check textStringLength + if (textStringLength <= 0 || textStringLength > 1024*1024) + return nullptr; + + IDWriteFactory* dwiteFactory = ON_IDWrite::Factory(); + if (nullptr == dwiteFactory) + return nullptr; + + HRESULT hr = -1; + IDWriteTextFormat* dwriteTextFormat = nullptr; + IDWriteTextLayout* dwriteTextLayout = nullptr; + + for (;;) + { + // dwriteFont is managed by opennurbs + const IDWriteFont* dwriteFont = font->WindowsDWriteFont(); + if (nullptr == dwriteFont) + break; + + dwriteTextFormat = Internal_TextFormat(dwriteFont); + if (nullptr == dwriteTextFormat) + break; + + FLOAT maxWidthInPixels = 16384.0f; // The width of the layout box. + FLOAT maxHeightInPixels = 128.0f; // The height of the layout box. + + hr = dwiteFactory->CreateTextLayout( + static_cast(textString), + textStringLength, + dwriteTextFormat, + maxWidthInPixels, + maxHeightInPixels, + &dwriteTextLayout + ); + if (nullptr == dwriteTextLayout) + break; + + FLOAT fontSizeInDIPs = 96.0; // The font size in DIP units to be set for text in the range specified by textRange. + + const DWRITE_TEXT_RANGE textRange = { 0, textStringLength }; + + if (SUCCEEDED(hr)) + hr = dwriteTextLayout->SetFontSize(fontSizeInDIPs, textRange); + + if (SUCCEEDED(hr) && font->IsUnderlined()) + hr = dwriteTextLayout->SetUnderline(TRUE, textRange); + + if (SUCCEEDED(hr) && font->IsStrikethrough()) + hr = dwriteTextLayout->SetStrikethrough(TRUE, textRange); + + break; + } + + if (SUCCEEDED(hr)) + return dwriteTextLayout; + + // delete stuff here + + return nullptr; +} + #endif diff --git a/opennurbs_wstring.cpp b/opennurbs_wstring.cpp index cbd95c2b..7459bbd3 100644 --- a/opennurbs_wstring.cpp +++ b/opennurbs_wstring.cpp @@ -424,7 +424,7 @@ wchar_t* ON_wString::ReserveArray( size_t array_capacity ) if (array_capacity <= 0) return nullptr; - if (array_capacity > (size_t)ON_String::MaximumStringLength) + if (array_capacity > (size_t)ON_wString::MaximumStringLength) { ON_ERROR("Requested capacity > ON_String::MaximumStringLength"); return nullptr; @@ -925,6 +925,156 @@ CFStringRef ON_String::ToAppleCFString() const } #endif + +bool ON_String::IsHexDigit(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +bool ON_String::IsDecimalDigit(char c) +{ + return (c >= '0' && c <= '9'); +} + + +bool ON_wString::IsHexDigit(wchar_t c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +bool ON_wString::IsDecimalDigit(wchar_t c) +{ + return (c >= '0' && c <= '9'); +} + + +bool ON_wString::IsDecimalDigit( + wchar_t c, + bool bOrdinaryDigitResult, + bool bSuperscriptDigitResult, + bool bSubscriptDigitResult +) +{ + if (bOrdinaryDigitResult && (c >= '0' && c <= '9')) + return true; + + if (bSuperscriptDigitResult) + { + switch (c) + { + case 0x2070: // 0 + case 0x00B9: // 1 + case 0x00B2: // 2 + case 0x00B3: // 3 + return true; + break; + } + if (c >= 0x2074 && c <= 0x2079) + return true; // 4,5,6,7,8,9 + } + + if (bSubscriptDigitResult && (c >= 0x2080 && c <= 0x2089)) + return true; + + return false; +} + +unsigned ON_wString::DecimalDigitFromWideChar( + wchar_t c, + bool bAcceptOrdinaryDigit, + bool bAcceptSuperscriptDigit, + bool bAcceptSubscriptDigit, + unsigned invalid_c_result +) +{ + if (bAcceptOrdinaryDigit && (c >= '0' && c <= '9')) + return (unsigned)(c - '0'); + + if (bAcceptSuperscriptDigit) + { + if (0x2070 == c || (c >= 0x2074 && c <= 0x2079)) + return (unsigned)(c - 0x2070); + else if (0x00B9 == c) + return 1; + else if (0x00B2 == c) + return 2; + else if (0x00B3 == c) + return 3; + } + + if (bAcceptSubscriptDigit && (c >= 0x2080 && c <= 0x2089)) + return (unsigned)(c - 0x2080); + + return invalid_c_result; +} + +int ON_wString::PlusOrMinusSignFromWideChar( + wchar_t c, + bool bAcceptOrdinarySign, + bool bAcceptSuperscriptSign, + bool bAcceptSubscriptSign + ) +{ + switch (c) + { + + case '+': // ordinary plus + case 0x2795: + return bAcceptOrdinarySign ? 1 : 0; + break; + + case '-': // ordinary hyphen-minus + case 0x2212: + case 0x2796: + return bAcceptOrdinarySign ? -1 : 0; + break; + + case 0x207A: // superscript + + return bAcceptSuperscriptSign ? 1 : 0; + break; + + case 0x207B: // superscript - + return bAcceptSuperscriptSign ? -1 : 0; + break; + + case 0x208A: // subscript + + return bAcceptSubscriptSign ? 1 : 0; + break; + + case 0x208B: // subscript - + return bAcceptSubscriptSign ? -1 : 0; + break; + } + + return 0; +} + + +bool ON_wString::IsSlash( + wchar_t c, + bool bOrdinarySlashResult, + bool bFractionSlashResult, + bool bDivisionSlashResult, + bool bMathematicalSlashResult +) +{ + switch (c) + { + case ON_UnicodeCodePoint::ON_Slash: + return bOrdinarySlashResult ? true : false; + case ON_UnicodeCodePoint::ON_FractionSlash: + return bFractionSlashResult ? true : false; + case ON_UnicodeCodePoint::ON_DivisionSlash: + return bDivisionSlashResult ? true : false; + case ON_UnicodeCodePoint::ON_MathimaticalSlash: + return bMathematicalSlashResult ? true : false; + default: + break; + } + + return false; +} + int ON_wString::Length() const { return Header()->string_length; @@ -1181,6 +1331,15 @@ const wchar_t* ON_wString::Array() const return ( Header()->string_capacity > 0 ) ? m_s : 0; } +const ON_wString ON_wString::Duplicate() const +{ + if (Length() <= 0) + return ON_wString::EmptyString; + ON_wString s = *this; + s.CopyArray(); + return s; +} + /* Returns: Total number of bytes of memory used by this class. @@ -2504,3 +2663,954 @@ ON_wString ON_wString::Right(int count) const return s; } +const ON_wString ON_wString::EncodeXMLValue() const +{ + return EncodeXMLValue(false); +} + +static unsigned Internal_ToHexDigits( + unsigned u, + unsigned* hex_digits, + size_t hex_buffer_capacity +) +{ + size_t hex_digit_count = 0; + while( hex_digit_count < hex_buffer_capacity) + { + hex_digits[hex_digit_count++] = u % 0x10; + u /= 0x10; + if (0 == u) + return ((unsigned)hex_digit_count); + } + return 0; +} + +const ON_wString ON_wString::EncodeXMLValue( + bool bEncodeCodePointsAboveBasicLatin +) const +{ + const int length0 = this->Length(); + if (length0 <= 0) + return ON_wString::EmptyString; + + const wchar_t* buffer0 = this->Array(); + if (nullptr == buffer0) + return ON_wString::EmptyString; + unsigned hex_digits[8] = {}; + const unsigned hex_digit_capacity = (unsigned)(sizeof(hex_digits) / sizeof(hex_digits[0])); + + const wchar_t* buffer0_end = buffer0 + length0; + int length1 = 0; + struct ON_UnicodeErrorParameters e; + for (const wchar_t* buffer1 = buffer0; buffer1 < buffer0_end; ++buffer1, ++length1) + { + const wchar_t c = *buffer1; + switch (c) + { + case ON_UnicodeCodePoint::ON_QuotationMark: + length1 += 5; + break; + case ON_UnicodeCodePoint::ON_Ampersand: + length1 += 4; + break; + case ON_UnicodeCodePoint::ON_Apostrophe: + length1 += 5; + break; + case ON_UnicodeCodePoint::ON_LessThanSign: + length1 += 3; + break; + case ON_UnicodeCodePoint::ON_GreaterThanSign: + length1 += 3; + break; + default: + if (bEncodeCodePointsAboveBasicLatin && (c < 0 || c > 127)) + { + e = ON_UnicodeErrorParameters::MaskErrors; + ON__UINT32 u = ON_UnicodeCodePoint::ON_ReplacementCharacter; + const int decoded_wchar_count = ON_DecodeWideChar(buffer1, (int)(buffer0_end - buffer1), &e, &u); + if (decoded_wchar_count > 0 && ON_IsValidUnicodeCodePoint(u)) + { + const unsigned hex_digit_count = Internal_ToHexDigits(u, hex_digits, hex_digit_capacity); + if (hex_digit_count > 0) + { + length1 += hex_digit_count; + length1 += 3; + buffer1 += (decoded_wchar_count-1); + } + } + } + break; + } + } + + if (length1 <= length0) + return *this; // nothing to encode + + ON_wString s; + wchar_t* encoded = s.ReserveArray(length1); + if (nullptr == encoded) + return ON_wString::EmptyString; // catastrophe + + for (const wchar_t* buffer1 = buffer0; buffer1 < buffer0_end; ++buffer1) + { + *encoded = *buffer1; + switch (*encoded) + { + case ON_UnicodeCodePoint::ON_QuotationMark: + *encoded++ = ON_wString::Ampersand; + *encoded++ = 'q'; + *encoded++ = 'u'; + *encoded++ = 'o'; + *encoded++ = 't'; + *encoded++ = ON_wString::Semicolon; + break; + + case ON_UnicodeCodePoint::ON_Ampersand: + *encoded++ = ON_wString::Ampersand; + *encoded++ = 'a'; + *encoded++ = 'm'; + *encoded++ = 'p'; + *encoded++ = ON_wString::Semicolon; + break; + + case ON_UnicodeCodePoint::ON_Apostrophe: + *encoded++ = ON_wString::Ampersand; + *encoded++ = 'a'; + *encoded++ = 'p'; + *encoded++ = 'o'; + *encoded++ = 's'; + *encoded++ = ON_wString::Semicolon; + break; + + case ON_UnicodeCodePoint::ON_LessThanSign: + *encoded++ = ON_wString::Ampersand; + *encoded++ = 'l'; + *encoded++ = 't'; + *encoded++ = ON_wString::Semicolon; + break; + + case ON_UnicodeCodePoint::ON_GreaterThanSign: + *encoded++ = ON_wString::Ampersand; + *encoded++ = 'g'; + *encoded++ = 't'; + *encoded++ = ON_wString::Semicolon; + break; + + default: + if (bEncodeCodePointsAboveBasicLatin && (*encoded < 0 || *encoded > 127)) + { + e = ON_UnicodeErrorParameters::MaskErrors; + ON__UINT32 u = ON_UnicodeCodePoint::ON_ReplacementCharacter; + const int decoded_wchar_count = ON_DecodeWideChar(buffer1, (int)(buffer0_end - buffer1), &e, &u); + if (decoded_wchar_count > 0 && ON_IsValidUnicodeCodePoint(u)) + { + unsigned hex_digit_count = Internal_ToHexDigits(u, hex_digits, hex_digit_capacity); + if (hex_digit_count > 0) + { + *encoded++ = ON_wString::Ampersand; + *encoded++ = ON_wString::NumberSign; + *encoded++ = 'x'; + while(hex_digit_count>0) + { + --hex_digit_count; + const unsigned h = hex_digits[hex_digit_count]; + if (h <= 9) + *encoded++ = (wchar_t)('0' + h); + else + *encoded++ = (wchar_t)('a' + (h - 10)); + } + *encoded = ON_wString::Semicolon; + buffer1 += (decoded_wchar_count - 1); + } + } + } + ++encoded; + } + } + *encoded = 0; + const int encoded_length = (int)(encoded - s.Array()); + if (encoded_length == length1) + { + s.SetLength(encoded_length); + return s; + } + + return ON_wString::EmptyString; // catastrophe! +} + +const ON_wString ON_wString::DecodeXMLValue() const +{ + const int length0 = this->Length(); + if (length0 <= 0) + return ON_wString::EmptyString; + + const wchar_t* buffer0 = this->Array(); + if (nullptr == buffer0) + return ON_wString::EmptyString; + + const wchar_t* buffer0_end = buffer0 + length0; + for (const wchar_t* buffer1 = buffer0; buffer1 < buffer0_end; ++buffer1) + { + if (ON_wString::Ampersand != *buffer1) + continue; + if (nullptr == ON_wString::ParseXMLCharacterEncoding(buffer1, (int)(buffer0_end - buffer1), 0, nullptr)) + continue; + + // need to copy and modify. + ON_wString s = this->Duplicate(); + if (s.Length() != length0) + return ON_wString::EmptyString; // catastrophe! + wchar_t* b0 = s.Array(); + if ( b0 == buffer0) + return ON_wString::EmptyString; // catastrophe! + + // skip what we've already parsed + wchar_t* b1 = b0 + (buffer1 - buffer0); + + // continue parsing and copying parsed results to s. + for (wchar_t c = 0; buffer1 < buffer0_end; *b1++ = c) + { + c = *buffer1; + if (ON_wString::Ampersand == c) + { + unsigned u = ON_UnicodeCodePoint::ON_InvalidCodePoint; + const wchar_t* buffer2 = ON_wString::ParseXMLCharacterEncoding(buffer1, (int)(buffer0_end - buffer1), u, &u); + if (buffer2 > buffer1) + { + buffer1 = buffer2; + wchar_t w[8] = {}; + const int wcount = ON_EncodeWideChar(u, sizeof(w) / sizeof(w[0]), w); + if (wcount >= 1) + { + for (int i = 0; i + 1 < wcount; ++i) + *b1++ = w[i]; // UTF-16 or UTF-8 encoding + c = w[wcount - 1]; + continue; + } + } + } + ++buffer1; + } + + // s is the decoded version of this. + s.SetLength(b1 - b0); + return s; + } + + // nothing to decode + return *this; +} + +bool ON_wString::IsXMLSpecialCharacter(wchar_t c) +{ + switch (c) + { + case ON_UnicodeCodePoint::ON_QuotationMark: + case ON_UnicodeCodePoint::ON_Ampersand: + case ON_UnicodeCodePoint::ON_Apostrophe: + case ON_UnicodeCodePoint::ON_LessThanSign: + case ON_UnicodeCodePoint::ON_GreaterThanSign: + return true; + break; + } + + return false; +} + +const wchar_t* ON_wString::ParseXMLUnicodeCodePointEncoding( + const wchar_t* buffer, + int buffer_length, + unsigned value_on_failure, + unsigned* unicode_code_point +) +{ + /* + QUICKLY parse an xml unicode code point encoding. + */ + if (nullptr != unicode_code_point) + *unicode_code_point = value_on_failure; + if (nullptr == buffer) + return nullptr; + if (-1 == buffer_length) + buffer_length = ON_wString::MaximumStringLength; + else if (buffer_length < 4) + return nullptr; + + if (ON_wString::Ampersand != buffer[0] || ON_wString::NumberSign != buffer[1]) + return nullptr; + + if (buffer_length >= 4 && ON_wString::IsDecimalDigit(buffer[2])) + { + // decimal encoding + unsigned n = 0U; + int i; + for (i = 2; i < buffer_length && n < ON_MaximumCodePoint && ON_wString::IsDecimalDigit(buffer[i]); ++i) + { + n = 10U * n + (unsigned)(buffer[i] - '0'); + } + if (i <= buffer_length && ON_wString::Semicolon == buffer[i] && ON_IsValidUnicodeCodePoint(n)) + { + if (nullptr != unicode_code_point) + *unicode_code_point = n; + return buffer + (i + 1); + } + } + else if (buffer_length >= 5 && 'x' == buffer[2] && ON_wString::IsHexDigit(buffer[3])) + { + // hexadecimal encoding + unsigned n = 0U; + int i; + for (i = 3; i < buffer_length && n < ON_MaximumCodePoint && ON_wString::IsHexDigit(buffer[i]); ++i) + { + const wchar_t c = buffer[i]; + if ('0' <= c && c <= '9') + n = 16U * n + (unsigned)(c - '0'); + else if ('a' <= c && c <= 'f') + n = 16U * n + 10U + (unsigned)(c - 'a'); + else if ('A' <= c && c <= 'F') + n = 16U * n + 10U + (unsigned)(c - 'A'); + else + break; + } + if (i <= buffer_length && ON_wString::Semicolon == buffer[i] && ON_IsValidUnicodeCodePoint(n)) + { + if (nullptr != unicode_code_point) + *unicode_code_point = n; + return buffer + (i + 1); + } + } + + return nullptr; +} + + +const wchar_t* ON_wString::ParseXMLCharacterEncoding( + const wchar_t* buffer, + int buffer_length, + unsigned value_on_failure, + unsigned* unicode_code_point +) +{ + if (nullptr != unicode_code_point) + *unicode_code_point = value_on_failure; + if (nullptr == buffer) + return nullptr; + if (buffer_length < 4 && -1 != buffer_length) + return nullptr; + + if (ON_wString::Ampersand != buffer[0]) + return nullptr; + + if (ON_UnicodeCodePoint::ON_NumberSign == buffer[1]) + return ParseXMLUnicodeCodePointEncoding(buffer, buffer_length, value_on_failure, unicode_code_point); + + if (-1 == buffer_length) + buffer_length = ON_wString::MaximumStringLength; + + unsigned u = 0; + switch(buffer[1]) + { + + case 'a': + if (buffer_length >= 5 + && 'm' == buffer[2] + && 'p' == buffer[3] + && ON_wString::Semicolon == buffer[4] + ) + { + buffer += 5; + u = ON_UnicodeCodePoint::ON_Ampersand; + } + else if (buffer_length >= 6 + && 'p' == buffer[2] + && 'o' == buffer[3] + && 's' == buffer[4] + && ON_wString::Semicolon == buffer[5] + ) + { + buffer += 6; + u = ON_UnicodeCodePoint::ON_Apostrophe; + } + break; + + case 'g': + if (buffer_length >= 4 + && 't' == buffer[2] + && ON_wString::Semicolon == buffer[3] + ) + { + buffer += 4; + u = ON_UnicodeCodePoint::ON_GreaterThanSign; + } + break; + + case 'l': + if (buffer_length >= 4 + && 't' == buffer[2] + && ON_wString::Semicolon == buffer[3] + ) + { + buffer += 4; + u = ON_UnicodeCodePoint::ON_LessThanSign; + } + break; + + case 'q': + if (buffer_length >= 6 + && 'u' == buffer[2] + && 'o' == buffer[3] + && 't' == buffer[4] + && ON_wString::Semicolon == buffer[5] + ) + { + buffer += 6; + u = ON_UnicodeCodePoint::ON_QuotationMark; + } + break; + } + + if (0 == u) + return nullptr; + + // successfully parsed + if (nullptr != unicode_code_point) + *unicode_code_point = u; + + return buffer; +} + +const ON_wString ON_wString::RichTextExample( + ON_wString rich_text_font_name, + bool bBold, + bool bItalic, + bool bBoldItalic, + bool bUnderline +) +{ + rich_text_font_name.TrimLeftAndRight(); + if (rich_text_font_name.IsEmpty()) + rich_text_font_name = ON_Font::Default.RichTextFontName(); + + // {\rtf1\deff0{\fonttbl{\f0 ;}} + // \f0 \fs23 + // {\f0 Rich Text Example:\par} + // {\f0 Regular}{\f0\ul underlined\par} + // {\f0\b Bold}{\f0\b\ul underlined\par} + // {\f0\i Italic}{\f0\i\ul underlined\par} + // {\f0\b\i Bold-Italic}{\f0\b\i\ul underlined\par} + // {\par}} + + ON_wString s = ON_wString(L"{\\rtf1\\deff0{\\fonttbl{\\f0 ") + rich_text_font_name + ON_wString(L";}}"); + + // Specify a base font and size + s += ON_wString(L"\\f0 \\fs23"); + + // Sample text + s += ON_wString(L"{\\f0 ") + rich_text_font_name + ON_wString(L" rich text example:\\par}"); + + s += ON_wString(L"{\\f0 Regular"); + if (bUnderline) + s += ON_wString(L" }{\\f0\\ul underlined"); + s += ON_wString(L"\\par}"); + + if (bBold) + { + s += ON_wString(L"{\\f0\\b Bold}"); + if (bUnderline) + s += ON_wString(L" }{\\f0\\b\\ul underlined"); + s += ON_wString(L"\\par}"); + } + + if (bItalic) + { + s += ON_wString(L"{\\f0\\i Italic}"); + if (bUnderline) + s += ON_wString(L" }{\\f0\\i\\ul underlined"); + s += ON_wString(L"\\par}"); + } + + if (bBoldItalic) + { + s += ON_wString(L"{\\f0\\b\\i Bold-Italic}"); + if (bUnderline) + s += ON_wString(L" }{\\f0\\b\\i\\ul underlined"); + s += ON_wString(L"\\par}"); + } + + return s; +} + +const ON_wString ON_wString::RichTextExample( + const class ON_FontFaceQuartet* quartet +) +{ + if (nullptr == quartet) + return ON_wString::Example(ON_wString::ExampleType::RichText); + return ON_wString::RichTextExample(quartet->QuartetName(), quartet->HasBoldFace(), quartet->HasItalicFace(), quartet->HasBoldItalicFace(), true); +} + +const ON_wString ON_wString::RichTextExample( + const ON_Font* font +) +{ + if (nullptr == font) + font = &ON_Font::Default; + const ON_FontFaceQuartet q = font->FontQuartet(); + if (q.IsNotEmpty()) + { + // restrict example to supported faces + // Many fonts (Arial Black, Corsiva, ...) do not have all 4 rich text faces. + return ON_wString::RichTextExample(q.QuartetName(), q.HasBoldFace(), q.HasItalicFace(), q.HasBoldItalicFace(), true); + } + + return ON_wString::RichTextExample(font->RichTextFontName(), true, true, true, true); +} + +const ON_wString ON_wString::Example(ON_wString::ExampleType t) +{ + ON_wString s; + switch (t) + { + case ON_wString::ExampleType::Empty: + break; + + case ON_wString::ExampleType::WideChar: + s = ON_wString( + ON_wString(L"The math teacher said, \"It isn't true that 2") + + ON_wString::Superscript3 + ON_wString(L"=3") + ON_wString::Superscript2 + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > 3") + + ON_wString::CentSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" < 2 ") + + ON_wString::RubleSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > ") + + ON_wString::EuroSign + + ON_wString(L"99.\" ") +#if defined(ON_SIZEOF_WCHAR_T) && ON_SIZEOF_WCHAR_T >= 4 + + ON_wString((wchar_t)0x1F5D1) // UTF-32 encoding for WASTEBASKET U+1F5D1 +#else + + ON_wString((wchar_t)0xD83D) // (0xD83D, 0xDDD1) is the UTF-16 surrogate pair encoding for WASTEBASKET U+1F5D1 + + ON_wString((wchar_t)0xDDD1) +#endif + + ON_wString(L"!") + ); + break; + + case ON_wString::ExampleType::UTF16: + s = ON_wString( + ON_wString(L"The math teacher said, \"It isn't true that 2") + + ON_wString::Superscript3 + ON_wString(L"=3") + ON_wString::Superscript2 + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > 3") + + ON_wString::CentSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" < 2 ") + + ON_wString::RubleSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > ") + + ON_wString::EuroSign + + ON_wString(L"99.\" ") + + ON_wString((wchar_t)(wchar_t)0xD83D) // (0xD83D, 0xDDD1) is the UTF-16 surrogate pair encoding for WASTEBASKET U+1F5D1 + + ON_wString((wchar_t)(wchar_t)0xDDD1) + + ON_wString(L"!") + ); + break; + + case ON_wString::ExampleType::RichText: + s = ON_wString::RichTextExample(&ON_Font::Default); + break; + + case ON_wString::ExampleType::XML: + /// The UTF string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin UTF encoded. + s = ON_wString( + ON_wString(L"The math teacher said, "It isn't true that 2") + + ON_wString::Superscript3 + ON_wString(L"=3") + ON_wString::Superscript2 + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > 3") + + ON_wString::CentSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" < 2 ") + + ON_wString::RubleSign + + ON_wString(L" & ") + + ON_wString::GreekCapitalSigma + + ON_wString(L" > ") + + ON_wString::EuroSign + + ON_wString(L"99." ") + + ON_wString((wchar_t)(wchar_t)0xD83D) // (0xD83D, 0xDDD1) is the UTF-16 surrogate pair encoding for WASTEBASKET U+1F5D1 + + ON_wString((wchar_t)(wchar_t)0xDDD1) + + ON_wString(L"!") + ); + break; + + case ON_wString::ExampleType::XMLalternate1: + /// The UTF string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin encoded in the &#hhhh; format + /// using lower case hex digits (0123456789abcdef). + s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); + break; + + case ON_wString::ExampleType::XMLalternate2: + /// The UTF string as an XML value with special characters encoded in the &amp; format + /// and code points above basic latin encoded in the hexadecimal &#xhhhh; format + /// with upper case hex digits (0123456789ABCDEF). + s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); + break; + + case ON_wString::ExampleType::XMLalternate3: + /// The UTF string as an XML value with special characters and code points above + /// basic latin encoded in the decimal code point &#nnnn; format. + s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); + break; + + default: + break; + } + + return s.IsNotEmpty() ? s : ON_wString::EmptyString; +} + +const ON_wString ON_wString::FormatToVulgarFraction(int numerator, int denominator) +{ + const bool bReduce = true; + const bool bMix = true; + const bool bUseVulgarFractionCodePoints = true; + return ON_wString::FormatToVulgarFraction(numerator, denominator, bReduce, bMix, 0, bUseVulgarFractionCodePoints); +} + +const ON_wString ON_wString::FormatToVulgarFraction( + int numerator, + int denominator, + bool bReduced, + bool bProper, + unsigned proper_fraction_separator_cp, + bool bUseVulgarFractionCodePoints +) +{ + if (0 == denominator) + { + // ... Kids these days! + return ON_wString::FormatToVulgarFraction(ON_wString::FormatToString(L"%d", numerator), L"0"); + } + + if (0 == numerator) + { + if (bReduced) + return ON_wString(L"0"); + if (bUseVulgarFractionCodePoints && 3 == numerator) + return ON_wString((wchar_t)0x2189); // Baseball zero for three 0/3 = U+2189 + + return ON_wString::FormatToVulgarFraction(L"0", ON_wString::FormatToString(L"%d", denominator)); + } + + if (bReduced || bProper) + { + if (denominator < 0) + { + denominator = -denominator; + numerator = -numerator; + } + } + + if (bReduced && abs(numerator) > 1 && abs(denominator) > 1) + { + const int gcd = (int)ON_GreatestCommonDivisor((unsigned)(abs(numerator)), (unsigned)denominator); + if (gcd > 0) + { + numerator /= gcd; + denominator /= gcd; + } + } + + int n = 0; + if (bProper && abs(numerator) >= denominator) + { + n = numerator / denominator; + numerator = abs(numerator - (n * denominator)); + if (0 == numerator) + return ON_wString::FormatToString(L"%d", n); + + if (0 != proper_fraction_separator_cp && false == ON_IsValidUnicodeCodePoint(proper_fraction_separator_cp)) + proper_fraction_separator_cp = 0; + } + + if (bUseVulgarFractionCodePoints && abs(numerator) < abs(denominator)) + { + unsigned fraction_cp = 0; + switch (denominator) + { + case 2: + if (1 == numerator) + fraction_cp = 0x00BD; + break; + case 3: + if (1 == numerator) + fraction_cp = 0x2153; + else if (2 == numerator) + fraction_cp = 0x2154; + break; + case 4: + if (1 == numerator) + fraction_cp = 0x00BC; + else if (3 == numerator) + fraction_cp = 0x00BE; + break; + case 5: + if (1 == numerator) + fraction_cp = 0x2155; + else if (2 == numerator) + fraction_cp = 0x2156; + else if (3 == numerator) + fraction_cp = 0x2157; + else if (4 == numerator) + fraction_cp = 0x2158; + break; + case 6: + if (1 == numerator) + fraction_cp = 0x2159; + else if (5 == numerator) + fraction_cp = 0x215A; + break; + case 7: + if (1 == numerator) + fraction_cp = 0x2150; + break; + case 8: + if (1 == numerator) + fraction_cp = 0x215B; + else if (3 == numerator) + fraction_cp = 0x215C; + else if (5 == numerator) + fraction_cp = 0x215D; + else if (7 == numerator) + fraction_cp = 0x215E; + break; + case 9: + if (1 == numerator) + fraction_cp = 0x2151; + break; + case 10: + if (1 == numerator) + fraction_cp = 0x2152; + break; + } + + if (fraction_cp > 0 && ON_IsValidUnicodeCodePoint(fraction_cp)) + { + unsigned cp[3] = {}; + unsigned cp_count = 0; + if (0 == n && numerator < 0) + cp[cp_count++] = ON_UnicodeCodePoint::ON_HyphenMinus; + cp[cp_count++] = fraction_cp; + const ON_wString fraction = ON_wString::FromUnicodeCodePoints(cp, cp_count, ON_UnicodeCodePoint::ON_ReplacementCharacter); + if (0 == n) + return fraction; + return ON_wString::FormatToString(L"%d", n) + + ON_wString::FromUnicodeCodePoint(proper_fraction_separator_cp) + + fraction; + } + } + + const ON_wString vulgar_fraction = ON_wString::FormatToVulgarFraction(ON_wString::FormatToString(L"%d", numerator), ON_wString::FormatToString(L"%d", denominator)); + return + (0 == n) + ? vulgar_fraction + : ON_wString::FormatToString(L"%d", n) + ON_wString::FromUnicodeCodePoint(proper_fraction_separator_cp) + vulgar_fraction; +} + +const ON_wString ON_wString::FormatToVulgarFraction( + const ON_wString numerator, + const ON_wString denominator +) +{ + return ON_wString::FormatToVulgarFractionNumerator(numerator) + ON_wString::VulgarFractionSlash() + ON_wString::FormatToVulgarFractionDenominator(denominator); +} + +static const ON_wString Internal_VulgarFractionXator(int updown, const ON_wString X) +{ + if (0 == updown) + return X; + + const int len = X.Length(); + if (len <= 0) + return ON_wString::EmptyString; + const wchar_t* s0 = X.Array(); + if (nullptr == s0) + return ON_wString::EmptyString; + + + bool bReturnAtor = false; + ON_wString ator; + ator.ReserveArray(len); + ON_UnicodeErrorParameters e; + int delta = 0; + for (int i = 0; i < len; i += ((delta > 0) ? delta : 1)) + { + e = ON_UnicodeErrorParameters::MaskErrors; + ON__UINT32 cp0 = ON_UnicodeCodePoint::ON_InvalidCodePoint; + delta = ON_DecodeWideChar(s0 + i, len - i, &e, &cp0); + ON__UINT32 cp1 + = (delta > 0 && ON_IsValidUnicodeCodePoint(cp0)) + ? (updown > 0 ? ON_UnicodeSuperscriptFromCodePoint(cp0,cp0) : ON_UnicodeSubcriptFromCodePoint(cp0,cp0)) + : ON_UnicodeCodePoint::ON_ReplacementCharacter; + if (cp1 != cp0 && cp1 != ON_UnicodeCodePoint::ON_ReplacementCharacter) + bReturnAtor = true; + ator += ON_wString::FromUnicodeCodePoint(cp1); + } + + return bReturnAtor ? ator : X; +} + + +const ON_wString ON_wString::FormatToVulgarFractionNumerator(const ON_wString numerator) +{ + return Internal_VulgarFractionXator(+1, numerator); +} + +const ON_wString ON_wString::FormatToVulgarFractionDenominator(const ON_wString denominator) +{ + return Internal_VulgarFractionXator(-1, denominator); +} + +const ON_wString ON_wString::VulgarFractionSlash() +{ + return ON_wString((wchar_t)0x2044); +} + +bool ON_wString::IsHorizontalSpace(wchar_t c, bool bTabResult, bool bNoBreakSpaceResult, bool bZeroWidthSpaceResult) +{ + if (((unsigned)c) < 0x2000U) + { + // extremely common values get a faster switch() statement + switch (c) + { + case ON_UnicodeCodePoint::ON_Tab: + return bTabResult ? true : false; + break; + + case ON_UnicodeCodePoint::ON_Space: + case ON_UnicodeCodePoint::ON_NoBreakSpace: + return true; + + default: + break; + } + } + else + { + switch (c) + { + case ON_UnicodeCodePoint::ON_OghamSpaceMark: + case ON_UnicodeCodePoint::ON_EnQuad: + case ON_UnicodeCodePoint::ON_EmQuad: + case ON_UnicodeCodePoint::ON_EnSpace: + case ON_UnicodeCodePoint::ON_EmSpace: + case ON_UnicodeCodePoint::ON_ThreePerEmSpace: + case ON_UnicodeCodePoint::ON_FourPerEmSpace: + case ON_UnicodeCodePoint::ON_SixPerEmSpace: + case ON_UnicodeCodePoint::ON_FigureSpace: + case ON_UnicodeCodePoint::ON_PunctuationSpace: + case ON_UnicodeCodePoint::ON_ThinSpace: + case ON_UnicodeCodePoint::ON_HairSpace: + case ON_UnicodeCodePoint::ON_MediumMathematicalSpace: + case ON_UnicodeCodePoint::ON_IdeographicSpace: + return true; + + case ON_UnicodeCodePoint::ON_NoBreakSpace: + case ON_UnicodeCodePoint::ON_NarrowNoBreakSpace: + return bNoBreakSpaceResult ? true : false; + break; + + case ON_UnicodeCodePoint::ON_ZeroWidthSpace: + case ON_UnicodeCodePoint::ON_ZeroWidthNonJoiner: + case ON_UnicodeCodePoint::ON_ZeroWidthJoiner: + return bZeroWidthSpaceResult ? true : false; + break; + + default: + break; + } + } + + return false; +} + +bool ON_wString::IsHorizontalSpace(wchar_t c) +{ + return ON_wString::IsHorizontalSpace(c, true, true, true); +} + +const wchar_t* ON_wString::ParseHorizontalSpace(const wchar_t* s, int len, bool bParseTab, bool bParseNoBreakSpace, bool bParseZeroWidthSpace) +{ + if (nullptr == s || len <= 0) + return nullptr; + + int i = 0; + for (wchar_t c = s[i]; i < len && ON_wString::IsHorizontalSpace(c, bParseTab, bParseNoBreakSpace, bParseZeroWidthSpace); c = s[++i]) + {/*empty body*/ + } + + return s + i; +} + +const wchar_t* ON_wString::ParseHorizontalSpace(const wchar_t* s, int len) +{ + return ON_wString::ParseHorizontalSpace(s, len, true, true, true); +} + +const wchar_t* ON_wString::ParseVulgarFraction(const wchar_t* s, int len, int& numerator, int& denomintor) +{ + numerator = 0; + denomintor = 0; + + if (nullptr == s) + return nullptr; + + if (-1 == len) + len = ON_wString::Length(s); + + if (len < 3) + return nullptr; + + + // / is permitted. + // / is permitted. + + const bool bOrdinary = ON_wString::IsDecimalDigit(*s, true, false, false); + const bool bSupSub = false == bOrdinary && ON_wString::IsDecimalDigit(*s, false, true, false); + if (false == bOrdinary || bSupSub) + return nullptr; + + int x = 0; + s = ON_wString::ToNumber(s, 0, &x); + if (nullptr == s) + return nullptr; + + if (ON_wString::IsSlash(*s,true,true,true,true)) + ++s; + else + return nullptr; + + if (false == ON_wString::IsDecimalDigit(*s, bOrdinary, false, bSupSub)) + return nullptr; + + int y = 0; + s = ON_wString::ToNumber(s, 0, &y); + if (nullptr == s) + return nullptr; + + numerator = x; + denomintor = y; + + return s; +} diff --git a/opennurbs_xform.cpp b/opennurbs_xform.cpp index 19c78c94..d74679d3 100644 --- a/opennurbs_xform.cpp +++ b/opennurbs_xform.cpp @@ -1639,6 +1639,15 @@ bool ON_Xform::IsZero4x4() const return (0.0 == m_xform[3][3] && IsZero()); } + +bool ON_Xform::IsZero4x4(double tol) const +{ + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + if (false == fabs(m_xform[i][j]) <= tol) return false; + return true; +} + bool ON_Xform::IsZeroTransformation() const { return IsZeroTransformation(0.0); @@ -1652,7 +1661,7 @@ bool ON_Xform::IsZeroTransformation(double tol) const { if (i == 3 && j == 3) continue; - rc = fabs(m_xform[i][j]) < tol; + rc = fabs(m_xform[i][j]) <= tol; } return (rc && 1.0 == m_xform[3][3] ); } diff --git a/opennurbs_xform.h b/opennurbs_xform.h index 5b8e2eb8..13d0d9e0 100644 --- a/opennurbs_xform.h +++ b/opennurbs_xform.h @@ -211,7 +211,7 @@ public: 0 0 0 * */ bool IsZero() const; - + /* Returns: true if matrix is ON_Xform::Zero4x4 @@ -222,7 +222,18 @@ public: 0 0 0 0 */ bool IsZero4x4() const; - + + /* + Returns: + true if matrix is ON_Xform::Zero4x4 + The value xform[3][3] must be zero. + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + */ + bool IsZero4x4(double zero_tolerance) const; + /* Returns: true if matrix is ON_Xform::ZeroTransformation diff --git a/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj b/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj index d8fb7b38..f7d09694 100644 --- a/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj +++ b/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj @@ -167,7 +167,7 @@ 1DB028151ED6430300FA9144 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1240; ORGANIZATIONNAME = "OpenNURBS 3dm File IO Toolkit"; TargetAttributes = { 1DB0281C1ED6430300FA9144 = { @@ -241,6 +241,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -297,6 +298,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -362,6 +364,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;