From d651f6740cbbece5ff72b34183666f973eb45f8f Mon Sep 17 00:00:00 2001 From: Oliver James Date: Tue, 15 Apr 2025 23:53:43 +0100 Subject: [PATCH] Fonts: Add OpenType feature support --- .../Builds/Android/app/CMakeLists.txt | 6 + .../VisualStudio2019/DemoRunner_App.vcxproj | 5 + .../DemoRunner_App.vcxproj.filters | 9 + .../VisualStudio2022/DemoRunner_App.vcxproj | 5 + .../DemoRunner_App.vcxproj.filters | 9 + .../Builds/Android/app/CMakeLists.txt | 6 + .../AudioPerformanceTest_App.vcxproj | 5 + .../AudioPerformanceTest_App.vcxproj.filters | 9 + .../Builds/Android/app/CMakeLists.txt | 6 + .../AudioPluginHost_App.vcxproj | 5 + .../AudioPluginHost_App.vcxproj.filters | 9 + .../AudioPluginHost_App.vcxproj | 5 + .../AudioPluginHost_App.vcxproj.filters | 9 + .../BinaryBuilder_ConsoleApp.vcxproj | 1 + .../BinaryBuilder_ConsoleApp.vcxproj.filters | 3 + .../Builds/Android/app/CMakeLists.txt | 6 + .../NetworkGraphicsDemo_App.vcxproj | 5 + .../NetworkGraphicsDemo_App.vcxproj.filters | 9 + .../VisualStudio2019/Projucer_App.vcxproj | 5 + .../Projucer_App.vcxproj.filters | 9 + .../VisualStudio2022/Projucer_App.vcxproj | 5 + .../Projucer_App.vcxproj.filters | 9 + .../UnitTestRunner_ConsoleApp.vcxproj | 5 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 9 + .../UnitTestRunner_ConsoleApp.vcxproj | 5 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 9 + .../WindowsDLL_DynamicLibrary.vcxproj | 5 + .../WindowsDLL_DynamicLibrary.vcxproj.filters | 9 + modules/juce_core/juce_core.h | 1 + .../misc/juce_OrderedContainerHelpers.h | 108 ++++++++++ .../detail/juce_SimpleShapedText.cpp | 193 +++++++++++++++++- modules/juce_graphics/fonts/juce_Font.cpp | 50 ++++- modules/juce_graphics/fonts/juce_Font.h | 31 +++ .../juce_graphics/fonts/juce_FontFeatures.cpp | 65 ++++++ .../juce_graphics/fonts/juce_FontFeatures.h | 143 +++++++++++++ .../juce_graphics/fonts/juce_FontOptions.cpp | 116 +++++++++++ .../juce_graphics/fonts/juce_FontOptions.h | 48 +++-- .../fonts/juce_GlyphArrangement.h | 2 + modules/juce_graphics/fonts/juce_Typeface.cpp | 186 ++++++++++++++++- modules/juce_graphics/fonts/juce_Typeface.h | 10 + modules/juce_graphics/juce_graphics.cpp | 1 + modules/juce_graphics/juce_graphics.h | 1 + 42 files changed, 1104 insertions(+), 33 deletions(-) create mode 100644 modules/juce_core/misc/juce_OrderedContainerHelpers.h create mode 100644 modules/juce_graphics/fonts/juce_FontFeatures.cpp create mode 100644 modules/juce_graphics/fonts/juce_FontFeatures.h diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index db6131893f..424e79fe01 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -1111,6 +1111,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1855,6 +1856,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" @@ -3771,6 +3774,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -4515,6 +4519,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index 5f8a3eed3e..f8d651942c 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -2160,6 +2160,9 @@ true + + true + true @@ -3902,6 +3905,7 @@ + @@ -4382,6 +4386,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 52a5a9990f..43f9a6aaef 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -2923,6 +2923,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -6006,6 +6009,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -7446,6 +7452,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index a2dc7771e0..0b52bac5d1 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -2160,6 +2160,9 @@ true + + true + true @@ -3902,6 +3905,7 @@ + @@ -4382,6 +4386,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index 8eb54cd7f0..252a8efa88 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -2923,6 +2923,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -6006,6 +6009,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -7446,6 +7452,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 1ae065577f..4183710b0f 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -970,6 +970,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1617,6 +1618,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" @@ -3244,6 +3247,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -3891,6 +3895,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index cb12398a5a..e3a7feab80 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -1851,6 +1851,9 @@ true + + true + true @@ -3368,6 +3371,7 @@ + @@ -3795,6 +3799,7 @@ + diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index db9d24942e..7056075e6f 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -2431,6 +2431,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5124,6 +5127,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6405,6 +6411,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index bf8ed75274..20c9ddc403 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -1003,6 +1003,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1747,6 +1748,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" @@ -3430,6 +3433,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -4174,6 +4178,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index d543831872..bbc7e8e1a3 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -1985,6 +1985,9 @@ true + + true + true @@ -3548,6 +3551,7 @@ + @@ -4028,6 +4032,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index 90e15a6859..07e48629e7 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -2638,6 +2638,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5400,6 +5403,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6840,6 +6846,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index 077b98bbff..ff565ab102 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -1985,6 +1985,9 @@ true + + true + true @@ -3548,6 +3551,7 @@ + @@ -4028,6 +4032,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index c1e2985b4c..e7c0f464e2 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -2638,6 +2638,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5400,6 +5403,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6840,6 +6846,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj index d5e0e5f1df..10de43b9d7 100644 --- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj +++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj @@ -586,6 +586,7 @@ + diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters index 8f72ed58a1..3e1cc4b4cd 100644 --- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters +++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters @@ -690,6 +690,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index 1b7042adb1..78e8221fdc 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -974,6 +974,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1636,6 +1637,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" @@ -3328,6 +3331,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" + "../../../../../modules/juce_core/misc/juce_OrderedContainerHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -3990,6 +3994,8 @@ set_source_files_properties( "../../../../../modules/juce_graphics/fonts/juce_AttributedString.h" "../../../../../modules/juce_graphics/fonts/juce_Font.cpp" "../../../../../modules/juce_graphics/fonts/juce_Font.h" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.cpp" + "../../../../../modules/juce_graphics/fonts/juce_FontFeatures.h" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.cpp" "../../../../../modules/juce_graphics/fonts/juce_FontOptions.h" "../../../../../modules/juce_graphics/fonts/juce_FunctionPointerDestructor.h" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 035ddec6bb..4ed818cbb0 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -1872,6 +1872,9 @@ true + + true + true @@ -3459,6 +3462,7 @@ + @@ -3893,6 +3897,7 @@ + diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index b6bf08b851..f582b5b1c6 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -2485,6 +2485,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5265,6 +5268,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6567,6 +6573,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index 61d0f4abb5..4bb5599491 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -1017,6 +1017,9 @@ true + + true + true @@ -2159,6 +2162,7 @@ + @@ -2593,6 +2597,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index 1cdcc5c0c8..b83130c888 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -1339,6 +1339,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -2913,6 +2916,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -4215,6 +4221,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj index ca3f64644c..906b2632f9 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj @@ -1017,6 +1017,9 @@ true + + true + true @@ -2159,6 +2162,7 @@ + @@ -2593,6 +2597,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters index 967264b9d1..59382cd1dc 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters @@ -1339,6 +1339,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -2913,6 +2916,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -4215,6 +4221,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index 62fab9dc3c..d4c1227a63 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -1993,6 +1993,9 @@ true + + true + true @@ -3660,6 +3663,7 @@ + @@ -4140,6 +4144,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index 3f3f2701a8..6ae5809260 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -2686,6 +2686,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5559,6 +5562,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6999,6 +7005,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index 0fb1939aca..3db9b5519d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -1993,6 +1993,9 @@ true + + true + true @@ -3660,6 +3663,7 @@ + @@ -4140,6 +4144,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index ea787cb498..ec23f1616a 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -2686,6 +2686,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5559,6 +5562,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6999,6 +7005,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj index 7983b3c5aa..ac404c44ab 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj @@ -1871,6 +1871,9 @@ true + + true + true @@ -3435,6 +3438,7 @@ + @@ -3869,6 +3873,7 @@ + diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters index 2aeb535a06..86fd263669 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters @@ -2482,6 +2482,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts @@ -5232,6 +5235,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6534,6 +6540,9 @@ JUCE Modules\juce_graphics\fonts + + JUCE Modules\juce_graphics\fonts + JUCE Modules\juce_graphics\fonts diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h index 2c61fbed2d..fd66714894 100644 --- a/modules/juce_core/juce_core.h +++ b/modules/juce_core/juce_core.h @@ -236,6 +236,7 @@ namespace juce } #include "misc/juce_EnumHelpers.h" +#include "misc/juce_OrderedContainerHelpers.h" #include "memory/juce_Memory.h" #include "maths/juce_MathsFunctions.h" #include "memory/juce_ByteOrder.h" diff --git a/modules/juce_core/misc/juce_OrderedContainerHelpers.h b/modules/juce_core/misc/juce_OrderedContainerHelpers.h new file mode 100644 index 0000000000..399ae849be --- /dev/null +++ b/modules/juce_core/misc/juce_OrderedContainerHelpers.h @@ -0,0 +1,108 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** A helper struct providing functions for managing sorted containers. + + These helper functions simplify common operations on containers that + are kept in a sorted order. + + @tags{Core} +*/ +struct OrderedContainerHelpers +{ + /** Returns true if neither value compares less than the other. + This is the same definition of equivalence as used by the std containers "set" and "map". + */ + template > + static constexpr bool equivalent (const A& a, const B& b, Less less = {}) + { + return ! less (a, b) && ! less (b, a); + } + + //============================================================================== + /** If the container already contains a value equivalent to the valueToInsert, assigns + the new value over the old one; otherwise, if no equivalent tag exists, inserts the + new value preserving the sorted property of the container. + */ + template > + static void insertOrAssign (OrderedContainer& container, + const ValueType& valueToInsert, + Less less = {}) + { + // This function won't do the right thing on a container that's not sorted! + jassert (std::is_sorted (container.begin(), container.end(), less)); + + auto iter = std::lower_bound (container.begin(), container.end(), valueToInsert, less); + + if (iter != container.end() && equivalent (*iter, valueToInsert, less)) + { + *iter = valueToInsert; + } + else + { + container.insert (iter, valueToInsert); + } + } + + /** Removes a specific element from a sorted array, preserving order. + + Searches for an element in the container that compares equivalent to valueToRemove and + erases it if present, preserving the sorted property of the container. + + Returns true if the array was modified or false otherwise (i.e. the element was not removed). + */ + template > + static bool remove (OrderedContainer& container, + const ValueType& valueToRemove, + Less less = {}) + { + // This function won't do the right thing on a container that's not sorted! + jassert (std::is_sorted (container.begin(), container.end(), less)); + + auto iter = std::lower_bound (container.begin(), container.end(), valueToRemove, less); + + if (iter == container.end() || ! equivalent (*iter, valueToRemove, less)) + return false; + + container.erase (iter); + return true; + } + + OrderedContainerHelpers() = delete; +}; + +} diff --git a/modules/juce_graphics/detail/juce_SimpleShapedText.cpp b/modules/juce_graphics/detail/juce_SimpleShapedText.cpp index d5b0019097..c70bd43ba0 100644 --- a/modules/juce_graphics/detail/juce_SimpleShapedText.cpp +++ b/modules/juce_graphics/detail/juce_SimpleShapedText.cpp @@ -165,6 +165,51 @@ static auto findControlCharacters (Span string) return result; } +static constexpr hb_feature_t hbFeature (FontFeatureSetting setting) +{ + return { setting.tag.getTag(), + setting.value, + HB_FEATURE_GLOBAL_START, + HB_FEATURE_GLOBAL_END }; +} + +enum class LigatureEnabledState +{ + normal, + disabled +}; + +static std::vector getHarfbuzzFeatures (Span settings, + LigatureEnabledState ligatureEnabledstate) +{ + // Font feature settings *should* always be sorted. + jassert (std::is_sorted (settings.begin(), settings.end())); + + std::vector features; + + features.reserve (settings.size()); + std::transform (settings.begin(), settings.end(), std::back_inserter (features), hbFeature); + + if (ligatureEnabledstate == LigatureEnabledState::disabled) + { + static constexpr FontFeatureTag tagsAffectedByTracking[] { FontFeatureTag { "liga" }, + FontFeatureTag { "clig" }, + FontFeatureTag { "hlig" }, + FontFeatureTag { "dlig" }, + FontFeatureTag { "calt" } }; + + static constexpr auto less = [] (hb_feature_t a, hb_feature_t b) + { + return a.tag < b.tag; + }; + + for (auto tag : tagsAffectedByTracking) + OrderedContainerHelpers::insertOrAssign (features, hbFeature ({ tag, 0 }), less); + } + + return features; +} + /* Returns glyphs in logical order as that favours wrapping. */ static std::vector lowLevelShape (Span string, Range range, @@ -201,16 +246,6 @@ static std::vector lowLevelShape (Span string, 0, 0); - std::vector features; - - // Disable ligatures if we're using non-standard tracking - const auto tracking = font.getExtraKerningFactor(); - const auto trackingIsDefault = approximatelyEqual (tracking, 0.0f, absoluteTolerance (0.001f)); - - if (! trackingIsDefault) - for (const auto key : { hbTag ("liga"), hbTag ("clig"), hbTag ("hlig"), hbTag ("dlig"), hbTag ("calt") }) - features.push_back (hb_feature_t { key, 0, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }); - hb_buffer_guess_segment_properties (buffer.get()); auto nativeFont = font.getNativeDetails().font; @@ -218,6 +253,13 @@ static std::vector lowLevelShape (Span string, if (nativeFont == nullptr) return {}; + const auto tracking = font.getExtraKerningFactor(); + const auto trackingIsDefault = approximatelyEqual (tracking, 0.0f, absoluteTolerance (0.001f)); + + const auto features = getHarfbuzzFeatures (font.getFeatureSettings(), + trackingIsDefault ? LigatureEnabledState::normal + : LigatureEnabledState::disabled); + hb_shape (nativeFont.get(), buffer.get(), features.data(), (unsigned int) features.size()); const auto [infos, positions] = [&buffer] @@ -1551,7 +1593,138 @@ struct SimpleShapedTextTests : public UnitTest } }; +class HarfbuzzFontFeatureTests : public UnitTest +{ +public: + HarfbuzzFontFeatureTests() + : UnitTest ("HarfbuzzFontFeatureTests", UnitTestCategories::text) + { + } + + void runTest() override + { + static constexpr FontFeatureSetting input[] = + { + FontFeatureSetting { "calt", 1 }, + FontFeatureSetting { "clig", 1 }, + FontFeatureSetting { "dlig", 1 }, + FontFeatureSetting { "hlig", 1 }, + FontFeatureSetting { "liga", 1 } + }; + + static constexpr auto compare = [] (hb_feature_t a, hb_feature_t b) + { + return a.value == b.value; + }; + + beginTest ("Disabling ligatures overrides existing ligature feature values in feature set"); + { + static constexpr hb_feature_t expected[] = + { + hbFeature ({ "calt", 0 }), + hbFeature ({ "clig", 0 }), + hbFeature ({ "dlig", 0 }), + hbFeature ({ "hlig", 0 }), + hbFeature ({ "liga", 0 }) + }; + + auto result = getHarfbuzzFeatures (input, LigatureEnabledState::disabled); + + expect (std::equal (result.begin(), + result.end(), + std::begin (expected), + std::end (expected), + compare)); + } + + beginTest ("Disabling ligatures appends ligature features in a disabled state to an empty feature set"); + { + static constexpr hb_feature_t expected[] = + { + hbFeature ({ "calt", 0 }), + hbFeature ({ "clig", 0 }), + hbFeature ({ "dlig", 0 }), + hbFeature ({ "hlig", 0 }), + hbFeature ({ "liga", 0 }) + }; + + auto result = getHarfbuzzFeatures ({}, LigatureEnabledState::disabled); + + expect (std::equal (result.begin(), + result.end(), + std::begin (expected), + std::end (expected), + compare)); + } + + beginTest ("Enabling ligatures has no effect on ligature features in feature set"); + { + static constexpr FontFeatureSetting featureSet[] = + { + FontFeatureSetting { "calt", 1 }, + FontFeatureSetting { "clig", 0 }, + FontFeatureSetting { "dlig", 1 }, + FontFeatureSetting { "hlig", 0 }, + FontFeatureSetting { "liga", 1 } + }; + + static constexpr hb_feature_t expected[] = + { + hbFeature ({ "calt", 1 }), + hbFeature ({ "clig", 0 }), + hbFeature ({ "dlig", 1 }), + hbFeature ({ "hlig", 0 }), + hbFeature ({ "liga", 1 }) + }; + + auto result = getHarfbuzzFeatures (featureSet, LigatureEnabledState::normal); + + expect (std::equal (result.begin(), + result.end(), + std::begin (expected), + std::end (expected), + compare)); + } + + beginTest ("Only ligature features are disabled in an existing feature set"); + { + static constexpr FontFeatureSetting featureSet[] = + { + FontFeatureSetting { "calt", 1 }, + FontFeatureSetting { "fak1", 0 }, + FontFeatureSetting { "fak2", 1 } + }; + + static constexpr hb_feature_t expected[] = + { + hbFeature ({ "calt", 0 }), + hbFeature ({ "clig", 0 }), + hbFeature ({ "dlig", 0 }), + hbFeature ({ "fak1", 0 }), + hbFeature ({ "fak2", 1 }), + hbFeature ({ "hlig", 0 }), + hbFeature ({ "liga", 0 }) + }; + + auto result = getHarfbuzzFeatures (featureSet, LigatureEnabledState::disabled); + + expect (std::equal (result.begin(), + result.end(), + std::begin (expected), + std::end (expected), + compare)); + } + + beginTest ("An empty feature set is not mutated when ligatures are enabled"); + { + auto result = getHarfbuzzFeatures ({}, LigatureEnabledState::normal); + expect (result.empty()); + } + } +}; + static SimpleShapedTextTests simpleShapedTextTests; +static HarfbuzzFontFeatureTests harfbuzzFontFeatureTests; #endif diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index 33ff25fac7..28f811f3a8 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -259,15 +259,28 @@ public: return StringArray (fallbacks.data(), (int) fallbacks.size()); } - String getTypefaceName() const { return options.getName(); } - String getTypefaceStyle() const { return options.getStyle(); } - float getHeight() const { return options.getHeight(); } - float getPointHeight() const { return options.getPointHeight(); } - float getHorizontalScale() const { return options.getHorizontalScale(); } - float getKerning() const { return options.getKerningFactor(); } - bool getUnderline() const { return options.getUnderline(); } - bool getFallbackEnabled() const { return options.getFallbackEnabled(); } - TypefaceMetricsKind getMetricsKind() const { return options.getMetricsKind(); } + String getTypefaceName() const { return options.getName(); } + String getTypefaceStyle() const { return options.getStyle(); } + float getHeight() const { return options.getHeight(); } + float getPointHeight() const { return options.getPointHeight(); } + float getHorizontalScale() const { return options.getHorizontalScale(); } + float getKerning() const { return options.getKerningFactor(); } + bool getUnderline() const { return options.getUnderline(); } + bool getFallbackEnabled() const { return options.getFallbackEnabled(); } + TypefaceMetricsKind getMetricsKind() const { return options.getMetricsKind(); } + auto getFeatureSettings() const { return options.getFeatureSettings(); } + + void setFeatureSetting (const FontFeatureSetting& feature) + { + jassert (getReferenceCount() == 1); + options = options.withFeatureSetting (feature); + } + + void removeFeatureSetting (FontFeatureTag feature) + { + jassert (getReferenceCount() == 1); + options = options.withFeatureRemoved (feature); + } std::optional getAscentOverride() const { return options.getAscentOverride(); } std::optional getDescentOverride() const { return options.getDescentOverride(); } @@ -728,6 +741,7 @@ std::optional Font::getAscentOverride() const noexcept void Font::setAscentOverride (std::optional x) { + dupeInternalIfShared(); font->setAscentOverride (x); } @@ -738,6 +752,7 @@ std::optional Font::getDescentOverride() const noexcept void Font::setDescentOverride (std::optional x) { + dupeInternalIfShared(); font->setDescentOverride (x); } @@ -750,6 +765,23 @@ bool Font::isUnderlined() const noexcept { return font->getUnderline(); } TypefaceMetricsKind Font::getMetricsKind() const noexcept { return font->getMetricsKind(); } +Span Font::getFeatureSettings() const& +{ + return font->getFeatureSettings(); +} + +void Font::setFeatureSetting (FontFeatureSetting featureSetting) +{ + dupeInternalIfShared(); + font->setFeatureSetting (featureSetting); +} + +void Font::removeFeatureSetting (FontFeatureTag featureToRemove) +{ + dupeInternalIfShared(); + font->removeFeatureSetting (featureToRemove); +} + void Font::setBold (const bool shouldBeBold) { auto flags = getStyleFlags(); diff --git a/modules/juce_graphics/fonts/juce_Font.h b/modules/juce_graphics/fonts/juce_Font.h index fba72f2045..3896e87d67 100644 --- a/modules/juce_graphics/fonts/juce_Font.h +++ b/modules/juce_graphics/fonts/juce_Font.h @@ -378,6 +378,37 @@ public: /** Returns the kind of metrics used by this Font. */ TypefaceMetricsKind getMetricsKind() const noexcept; + //============================================================================== + /** Returns an Span view of the features configured for this font instance. + + Use Typeface::getSupportedFeatures() to determine what features this font + supports. + + @see setFeatureEnabled, setFeatureDisabled, removeFeature, + Typeface::getSupportedFeatures + */ + Span getFeatureSettings() const&; + Span getFeatureSettings() const&& = delete; + + /** Enables or disables a font feature. + + Use Typeface::getSupportedFeatures() to determine what features this font + supports. + + @see setFeatureEnabled, setFeatureDisabled, removeFeature, Typeface::getSupportedFeatures + */ + void setFeatureSetting (FontFeatureSetting featureSetting); + + /** Removes a specific feature setting from this font. + + If `featureToRemove` corresponds to a feature that is typically enabled by default (e.g., + "calt" for contextual alternates, "liga" for standard ligatures), calling this method will + reset it to its default-enabled state. + + @see setFeatureEnabled, setFeatureDisabled, Typeface::getSupportedFeatures + */ + void removeFeatureSetting (FontFeatureTag featureToRemove); + //============================================================================== /** Returns the font's horizontal scale. A value of 1.0 is the normal scale, less than this will be narrower, greater diff --git a/modules/juce_graphics/fonts/juce_FontFeatures.cpp b/modules/juce_graphics/fonts/juce_FontFeatures.cpp new file mode 100644 index 0000000000..be967f4461 --- /dev/null +++ b/modules/juce_graphics/fonts/juce_FontFeatures.cpp @@ -0,0 +1,65 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +FontFeatureTag FontFeatureTag::fromString (const String& string) +{ + if (string.getNumBytesAsUTF8() != 4) + { + jassertfalse; + return FontFeatureTag (0); + } + + char characters[5]{}; + memcpy (characters, string.toRawUTF8(), 4); + return FontFeatureTag { characters }; +} + +String FontFeatureTag::toString() const +{ + const char characters[5] + { + static_cast (tag >> 24), + static_cast (tag >> 16), + static_cast (tag >> 8), + static_cast (tag), + '\0' + }; + + return String { characters }; +} + +} // namespace juce diff --git a/modules/juce_graphics/fonts/juce_FontFeatures.h b/modules/juce_graphics/fonts/juce_FontFeatures.h new file mode 100644 index 0000000000..8a1eee109b --- /dev/null +++ b/modules/juce_graphics/fonts/juce_FontFeatures.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ +/** Represents a single OpenType font feature. + + OpenType font features are typographic options that can be enabled or disabled + to control text rendering behavior. Each feature is identified by a + four-character tag (like 'liga' for standard ligatures or 'smcp' for small caps). + These features allow fine-grained control over how text is displayed, including + ligatures, number styles, stylistic alternates, and positioning adjustments. + + The tag must be exactly 4 characters long. + + @see FontOptions, Font + + @tags{Graphics} +*/ +class JUCE_API FontFeatureTag final +{ +public: + /** Constructs a feature from the specified tag string. */ + constexpr FontFeatureTag (const char (&string)[5]) + : tag { (uint32) string[0] << 24 + | (uint32) string[1] << 16 + | (uint32) string[2] << 8 + | (uint32) string[3] } + { + } + + /** Constructs a feature from the specified tag value. */ + constexpr explicit FontFeatureTag (uint32 tagValue) + : tag (tagValue) + { + } + + /** Creates a new FontFeature with the specified tag. */ + [[nodiscard]] static FontFeatureTag fromString (const String& tagString); + + /** Returns a string representation of this tag. */ + String toString() const; + + /** Returns the Harfbuzz compatible OpenType tag as an unsigned 32-bit integer. */ + constexpr uint32 getTag() const { return tag; } + + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator< (FontFeatureTag other) const { return tag < other.tag; } + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator<= (FontFeatureTag other) const { return tag <= other.tag; } + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator> (FontFeatureTag other) const { return tag > other.tag; } + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator>= (FontFeatureTag other) const { return tag >= other.tag; } + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator== (FontFeatureTag other) const { return tag == other.tag; } + /** Comparison based on tag value. */ + [[nodiscard]] constexpr bool operator!= (FontFeatureTag other) const { return tag != other.tag; } + +private: + FontFeatureTag() = default; + uint32 tag; +}; + +/** Represents a single OpenType font feature setting. + + A font feature setting combines a FontFeatureTag with an optional value that + controls the behavior of that feature. For example, a 'liga' (standard ligatures) + feature can be enabled (value=1) or disabled (value=0), while other features like + 'salt' (stylistic alternates) might accept a range of values to select specific + alternates. + + @see FontFeatureTag, FontOptions, Font + + @tags{Graphics} +*/ +class JUCE_API FontFeatureSetting final +{ + constexpr auto tie() const { return std::tuple (tag, value); } + +public: + /** Common feature values for convenience. */ + /** Enable this feature (value=1). */ + static constexpr auto featureEnabled = 1; + /** Disable this feature (value=0). */ + static constexpr auto featureDisabled = 0; + + /** Constructs a feature setting with the specified tag and optional value. */ + constexpr FontFeatureSetting (FontFeatureTag featureTag, uint32 featureValue) noexcept + : tag (featureTag), + value (featureValue) + { + } + + [[nodiscard]] constexpr bool operator< (const FontFeatureSetting& other) const { return tie() < other.tie(); } + [[nodiscard]] constexpr bool operator<= (const FontFeatureSetting& other) const { return tie() <= other.tie(); } + [[nodiscard]] constexpr bool operator> (const FontFeatureSetting& other) const { return tie() > other.tie(); } + [[nodiscard]] constexpr bool operator>= (const FontFeatureSetting& other) const { return tie() >= other.tie(); } + [[nodiscard]] constexpr bool operator== (const FontFeatureSetting& other) const { return tie() == other.tie(); } + [[nodiscard]] constexpr bool operator!= (const FontFeatureSetting& other) const { return tie() != other.tie(); } + + /** The OpenType feature tag. */ + FontFeatureTag tag; + + /** The value for this feature. + Common values are 0 (featureDisabled) and 1 (featureEnabled), but some features + support additional values for specific behaviors. + */ + uint32 value; +}; + +} // namespace juce diff --git a/modules/juce_graphics/fonts/juce_FontOptions.cpp b/modules/juce_graphics/fonts/juce_FontOptions.cpp index 2b756c447e..0b750957d3 100644 --- a/modules/juce_graphics/fonts/juce_FontOptions.cpp +++ b/modules/juce_graphics/fonts/juce_FontOptions.cpp @@ -92,6 +92,7 @@ auto FontOptions::tie() const style, typeface.get(), fallbacks, + features, metricsKind, ascentOverride, descentOverride, @@ -103,6 +104,42 @@ auto FontOptions::tie() const underlined); } +struct FontFeatureSettingSortHelper +{ + bool operator() (FontFeatureSetting a, FontFeatureSetting b) const + { + return a.tag < b.tag; + } + + bool operator() (FontFeatureTag a, FontFeatureSetting b) const + { + return a < b.tag; + } + + bool operator() (FontFeatureSetting a, FontFeatureTag b) const + { + return a.tag < b; + } +}; + +FontOptions FontOptions::withFeatureSetting (FontFeatureSetting newSetting) const +{ + auto copy = *this; + + OrderedContainerHelpers::insertOrAssign (copy.features, newSetting, FontFeatureSettingSortHelper{}); + + return copy; +} + +FontOptions FontOptions::withFeatureRemoved (FontFeatureTag featureTag) const +{ + auto copy = *this; + + OrderedContainerHelpers::remove (copy.features, featureTag, FontFeatureSettingSortHelper{}); + + return copy; +} + bool FontOptions::operator== (const FontOptions& other) const { return tie() == other.tie(); } bool FontOptions::operator!= (const FontOptions& other) const { return tie() != other.tie(); } bool FontOptions::operator< (const FontOptions& other) const { return tie() < other.tie(); } @@ -110,4 +147,83 @@ bool FontOptions::operator<= (const FontOptions& other) const { return tie() <= bool FontOptions::operator> (const FontOptions& other) const { return tie() > other.tie(); } bool FontOptions::operator>= (const FontOptions& other) const { return tie() >= other.tie(); } +#if JUCE_UNIT_TESTS + +class FontFeatureContainerTests : public UnitTest +{ +public: + FontFeatureContainerTests() : UnitTest ("FontFeatureContainerTests", UnitTestCategories::text) + { + } + + void runTest() override + { + beginTest ("Features can be enabled"); + { + const auto options = FontOptions{}.withFeatureEnabled ("clig"); + + expectEquals ((int) options.getFeatureSettings().size(), 1); + expect (compareFeatureLists (options.getFeatureSettings(), + { + FontFeatureSetting ("clig", FontFeatureSetting::featureEnabled) + })); + } + + beginTest ("Features can be disabled"); + { + const auto options = FontOptions{}.withFeatureDisabled ("clig"); + + expectEquals ((int) options.getFeatureSettings().size(), 1); + expect (compareFeatureLists (options.getFeatureSettings(), + { + FontFeatureSetting ("clig", FontFeatureSetting::featureDisabled) + })); + } + + beginTest ("Features can be removed"); + { + const auto options = FontOptions{}.withFeatureEnabled ("clig") + .withFeatureRemoved ("clig"); + + expectEquals ((int) options.getFeatureSettings().size(), 0); + expect (compareFeatureLists (options.getFeatureSettings(), {})); + } + + beginTest ("Duplicate features are not allowed"); + { + const auto options = FontOptions{}.withFeatureEnabled ("clig") + .withFeatureEnabled ("clig"); + + expectEquals ((int) options.getFeatureSettings().size(), 1); + } + + beginTest ("Features are always sorted by tag"); + { + const auto options = FontOptions{}.withFeatureEnabled ("clig") + .withFeatureDisabled ("blig") + .withFeatureEnabled ("alig"); + + expectEquals ((int) options.getFeatureSettings().size(), 3); + expect (compareFeatureLists (options.getFeatureSettings(), + { + FontFeatureSetting ("alig", FontFeatureSetting::featureEnabled), + FontFeatureSetting ("blig", FontFeatureSetting::featureDisabled), + FontFeatureSetting ("clig", FontFeatureSetting::featureEnabled) + })); + } + } + +private: + static bool compareFeatureLists (Span input, + std::initializer_list expected) + { + return std::equal (input.begin(), input.end(), expected.begin(), expected.end()); + } +}; + +static FontFeatureContainerTests fontFeatureContainerTests; + +#endif + + } // namespace juce diff --git a/modules/juce_graphics/fonts/juce_FontOptions.h b/modules/juce_graphics/fonts/juce_FontOptions.h index 266818d778..0a010b8ec6 100644 --- a/modules/juce_graphics/fonts/juce_FontOptions.h +++ b/modules/juce_graphics/fonts/juce_FontOptions.h @@ -176,32 +176,53 @@ public: */ [[nodiscard]] FontOptions withDescentOverride (std::optional x) const { return withMember (*this, &FontOptions::descentOverride, x.value_or (-1.0f)); } + /** Returns a copy of these options with the specified font feature setting added or updated. */ + [[nodiscard]] FontOptions withFeatureSetting (FontFeatureSetting featureSetting) const; + + /** Returns a copy of these options with the specified feature removed. + + If the `featureTag` corresponds to a recognised default-enabled font feature (e.g., "calt", + "ccmp", "liga", "locl", "mark", "mkmk", "rlig"), it's setting will be reset to its default + state. + */ + [[nodiscard]] FontOptions withFeatureRemoved (FontFeatureTag featureTag) const; + + /** Returns a copy of these options with the specified feature enabled. */ + [[nodiscard]] FontOptions withFeatureEnabled (FontFeatureTag tag) const { return withFeatureSetting ({ tag, FontFeatureSetting::featureEnabled }); } + + /** Returns a copy of these options with the specified feature disabled. */ + [[nodiscard]] FontOptions withFeatureDisabled (FontFeatureTag tag) const { return withFeatureSetting ({ tag, FontFeatureSetting::featureDisabled }); } + /** @see withName() */ - [[nodiscard]] auto getName() const { return name; } + [[nodiscard]] auto getName() const { return name; } /** @see withStyle() */ - [[nodiscard]] auto getStyle() const { return style; } + [[nodiscard]] auto getStyle() const { return style; } /** @see withTypeface() */ - [[nodiscard]] auto getTypeface() const { return typeface; } + [[nodiscard]] auto getTypeface() const { return typeface; } /** @see withFallbacks() */ - [[nodiscard]] auto getFallbacks() const { return fallbacks; } + [[nodiscard]] auto getFallbacks() const { return fallbacks; } /** @see withHeight() */ - [[nodiscard]] auto getHeight() const { return height; } + [[nodiscard]] auto getHeight() const { return height; } /** @see withPointHeight() */ - [[nodiscard]] auto getPointHeight() const { return pointHeight; } + [[nodiscard]] auto getPointHeight() const { return pointHeight; } /** @see withKerningFactor() */ - [[nodiscard]] auto getKerningFactor() const { return tracking; } + [[nodiscard]] auto getKerningFactor() const { return tracking; } /** @see withHorizontalScale() */ - [[nodiscard]] auto getHorizontalScale() const { return horizontalScale; } + [[nodiscard]] auto getHorizontalScale() const { return horizontalScale; } /** @see withFallbackEnabled() */ - [[nodiscard]] auto getFallbackEnabled() const { return fallbackEnabled; } + [[nodiscard]] auto getFallbackEnabled() const { return fallbackEnabled; } /** @see withUnderline() */ - [[nodiscard]] auto getUnderline() const { return underlined; } + [[nodiscard]] auto getUnderline() const { return underlined; } /** @see withMetricsKind() */ - [[nodiscard]] auto getMetricsKind() const { return metricsKind; } + [[nodiscard]] auto getMetricsKind() const { return metricsKind; } /** @see withAscentOverride() */ - [[nodiscard]] auto getAscentOverride() const { return ascentOverride >= 0.0f ? std::make_optional (ascentOverride) : std::nullopt; } + [[nodiscard]] auto getAscentOverride() const { return ascentOverride >= 0.0f ? std::make_optional (ascentOverride) : std::nullopt; } /** @see withDescentOverride() */ - [[nodiscard]] auto getDescentOverride() const { return descentOverride >= 0.0f ? std::make_optional (descentOverride) : std::nullopt; } + [[nodiscard]] auto getDescentOverride() const { return descentOverride >= 0.0f ? std::make_optional (descentOverride) : std::nullopt; } + + /** @see withFeatureSetting() */ + [[nodiscard]] Span getFeatureSettings() const& { return features; } + [[nodiscard]] Span getFeatureSettings() const&& = delete; /** Equality operator. */ [[nodiscard]] bool operator== (const FontOptions& other) const; @@ -222,6 +243,7 @@ private: String name, style; Typeface::Ptr typeface; std::vector fallbacks; + std::vector features; TypefaceMetricsKind metricsKind { TypefaceMetricsKind::portable }; float height = -1.0f; float pointHeight = -1.0f; diff --git a/modules/juce_graphics/fonts/juce_GlyphArrangement.h b/modules/juce_graphics/fonts/juce_GlyphArrangement.h index 07c1a1b02a..798b76c641 100644 --- a/modules/juce_graphics/fonts/juce_GlyphArrangement.h +++ b/modules/juce_graphics/fonts/juce_GlyphArrangement.h @@ -73,6 +73,8 @@ public: float getBottom() const { return y + font.getDescent(); } /** Returns the bounds of the glyph. */ Rectangle getBounds() const { return { x, getTop(), w, font.getHeight() }; } + /** Returns the typeface glyph index for the glyph. */ + int getGlyphIndex() const { return glyph; } //============================================================================== /** Shifts the glyph's position by a relative amount. */ diff --git a/modules/juce_graphics/fonts/juce_Typeface.cpp b/modules/juce_graphics/fonts/juce_Typeface.cpp index dd87b50126..ede83f4e36 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.cpp +++ b/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -727,6 +727,50 @@ void Typeface::getGlyphPositions (TypefaceMetricsKind kind, xOffsets.add (width); } +std::vector Typeface::getSupportedFeatures() const +{ + std::vector features; + + static constexpr hb_tag_t tagTables[] + { + HB_OT_TAG_GPOS, + HB_OT_TAG_GSUB + }; + + auto* face = hb_font_get_face (getNativeDetails().getFont()); + + for (auto table : tagTables) + { + auto featureCount = hb_ot_layout_table_get_feature_tags (face, + table, + 0, + nullptr, + nullptr); + + if (featureCount == 0) + continue; + + std::vector arr; + arr.resize (featureCount); + features.reserve (features.size() + arr.size()); + hb_ot_layout_table_get_feature_tags (face, + table, + 0, + &featureCount, + arr.data()); + + std::transform (arr.begin(), arr.end(), std::back_inserter (features), [] (hb_tag_t tag) + { + return FontFeatureTag { (uint32) tag }; + }); + } + + std::sort (features.begin(), features.end()); + features.erase (std::unique (features.begin(), features.end()), features.end()); + + return features; +} + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -1009,11 +1053,151 @@ public: R"({"name":"Karla_Regular_Typo_Off_Offsets_Off","os":1024,"ascent":0.798751175403595,"descent":0.201248824596405,"heightToPoints":0.798751175403595,"A":[{"g":116,"a":0.3611911535263062},{"g":104,"a":0.5797309875488281},{"g":101,"a":0.4932756423950195},{"g":32,"a":0.228145956993103},{"g":113,"a":0.5614793300628662},{"g":117,"a":0.5816521644592285},{"g":105,"a":0.2862632274627686},{"g":99,"a":0.4956772327423096},{"g":107,"a":0.5413064956665039},{"g":32,"a":0.2281460762023926},{"g":98,"a":0.5691642761230469},{"g":114,"a":0.3472623825073242},{"g":111,"a":0.5249757766723633},{"g":119,"a":0.6959652900695801},{"g":110,"a":0.5797309875488281},{"g":32,"a":0.2281460762023926},{"g":102,"a":0.3290104866027832},{"g":111,"a":0.5249757766723633},{"g":120,"a":0.4913539886474609},{"g":32,"a":0.2281455993652344},{"g":106,"a":0.3011531829833984},{"g":117,"a":0.5816526412963867},{"g":109,"a":0.8986549377441406},{"g":112,"a":0.5614795684814453},{"g":115,"a":0.5048027038574219},{"g":32,"a":0.2281455993652344},{"g":111,"a":0.5249757766723633},{"g":118,"a":0.4827089309692383},{"g":101,"a":0.4932756423950195},{"g":114,"a":0.3472623825073242},{"g":32,"a":0.2281455993652344},{"g":116,"a":0.3611907958984375},{"g":104,"a":0.5797309875488281},{"g":101,"a":0.4932756423950195},{"g":32,"a":0.2281455993652344},{"g":108,"a":0.2689723968505859},{"g":97,"a":0.5288181304931641},{"g":122,"a":0.4558124542236328},{"g":121,"a":0.4471664428710938},{"g":32,"a":0.2281455993652344},{"g":100,"a":0.5691642761230469},{"g":111,"a":0.5249767303466797},{"g":103,"a":0.542266845703125}],"B":[{"g":83,"a":0.5806916356086731},{"g":80,"a":0.5317003130912781},{"g":72,"a":0.6421709060668945},{"g":73,"a":0.2689721584320068},{"g":78,"a":0.6570603847503662},{"g":88,"a":0.616234302520752},{"g":32,"a":0.2281460762023926},{"g":79,"a":0.6200766563415527},{"g":70,"a":0.5033621788024902},{"g":32,"a":0.2281460762023926},{"g":66,"a":0.5970220565795898},{"g":76,"a":0.4490876197814941},{"g":65,"a":0.5518732070922852},{"g":67,"a":0.5888566970825195},{"g":75,"a":0.5835733413696289},{"g":32,"a":0.2281460762023926},{"g":81,"a":0.6258406639099121},{"g":85,"a":0.6272811889648438},{"g":65,"a":0.5518732070922852},{"g":82,"a":0.5883769989013672},{"g":84,"a":0.4807872772216797},{"g":90,"a":0.5682039260864258},{"g":32,"a":0.2281455993652344},{"g":74,"a":0.387608528137207},{"g":85,"a":0.6272811889648438},{"g":68,"a":0.6340055465698242},{"g":71,"a":0.6186361312866211},{"g":69,"a":0.5398654937744141},{"g":32,"a":0.2281455993652344},{"g":77,"a":0.8136405944824219},{"g":89,"a":0.5268974304199219},{"g":32,"a":0.2281455993652344},{"g":86,"a":0.5403461456298828},{"g":79,"a":0.6200771331787109},{"g":87,"a":0.8587894439697266}]})", R"({"name":"Karla_Regular_Typo_On_Offsets_Off","os":1024,"ascent":0.7691982984542847,"descent":0.2308017015457153,"heightToPoints":0.7691982984542847,"A":[{"g":116,"a":0.3172995746135712},{"g":104,"a":0.5092827081680298},{"g":101,"a":0.4333332777023315},{"g":32,"a":0.200421929359436},{"g":113,"a":0.4932489395141602},{"g":117,"a":0.5109704732894897},{"g":105,"a":0.2514767646789551},{"g":99,"a":0.4354431629180908},{"g":107,"a":0.4755275249481201},{"g":32,"a":0.2004220485687256},{"g":98,"a":0.5},{"g":114,"a":0.3050632476806641},{"g":111,"a":0.461181640625},{"g":119,"a":0.6113924980163574},{"g":110,"a":0.5092825889587402},{"g":32,"a":0.2004218101501465},{"g":102,"a":0.289029598236084},{"g":111,"a":0.461181640625},{"g":120,"a":0.431645393371582},{"g":32,"a":0.2004218101501465},{"g":106,"a":0.264556884765625},{"g":117,"a":0.5109701156616211},{"g":109,"a":0.7894515991210938},{"g":112,"a":0.4932489395141602},{"g":115,"a":0.4434595108032227},{"g":32,"a":0.2004222869873047},{"g":111,"a":0.461181640625},{"g":118,"a":0.4240503311157227},{"g":101,"a":0.4333333969116211},{"g":114,"a":0.3050632476806641},{"g":32,"a":0.2004222869873047},{"g":116,"a":0.3172998428344727},{"g":104,"a":0.5092830657958984},{"g":101,"a":0.4333333969116211},{"g":32,"a":0.2004222869873047},{"g":108,"a":0.2362871170043945},{"g":97,"a":0.4645566940307617},{"g":122,"a":0.4004220962524414},{"g":121,"a":0.392827033996582},{"g":32,"a":0.2004222869873047},{"g":100,"a":0.5},{"g":111,"a":0.4611806869506836},{"g":103,"a":0.4763717651367188}],"B":[{"g":83,"a":0.5101265907287598},{"g":80,"a":0.4670885801315308},{"g":72,"a":0.564134955406189},{"g":73,"a":0.2362868785858154},{"g":78,"a":0.5772151947021484},{"g":88,"a":0.5413501262664795},{"g":32,"a":0.2004220485687256},{"g":79,"a":0.5447256565093994},{"g":70,"a":0.4421942234039307},{"g":32,"a":0.2004218101501465},{"g":66,"a":0.524472713470459},{"g":76,"a":0.3945145606994629},{"g":65,"a":0.4848103523254395},{"g":67,"a":0.5172996520996094},{"g":75,"a":0.5126581192016602},{"g":32,"a":0.2004218101501465},{"g":81,"a":0.5497889518737793},{"g":85,"a":0.5510544776916504},{"g":65,"a":0.4848098754882812},{"g":82,"a":0.5168771743774414},{"g":84,"a":0.42236328125},{"g":90,"a":0.4991559982299805},{"g":32,"a":0.2004222869873047},{"g":74,"a":0.3405065536499023},{"g":85,"a":0.5510549545288086},{"g":68,"a":0.5569620132446289},{"g":71,"a":0.5434598922729492},{"g":69,"a":0.4742612838745117},{"g":32,"a":0.2004222869873047},{"g":77,"a":0.7147674560546875},{"g":89,"a":0.4628696441650391},{"g":32,"a":0.2004222869873047},{"g":86,"a":0.4746837615966797},{"g":79,"a":0.5447254180908203},{"g":87,"a":0.7544307708740234}]})", }; - }; static TypefaceTests typefaceTests; +class FontFeatureTests : public UnitTest +{ +public: + FontFeatureTests() : UnitTest ("Font Features", UnitTestCategories::graphics) {} + + void runTest() override + { + auto block = unpackFontData(); + auto typeface = Typeface::createSystemTypefaceFor (block.getData(), block.getSize()); + + beginTest ("Check Typeface::getSupportedFeatures detects GSUB table features"); + expect (std::invoke ([&] + { + const auto features = typeface->getSupportedFeatures(); + return std::find (features.begin(), features.end(), "aalt") != features.end(); + })); + + Array glyphs; + Array offsets; + typeface->getGlyphPositions (TypefaceMetricsKind::legacy, "AD", glyphs, offsets); + + const auto aIndex = glyphs[0]; + const auto dIndex = glyphs[1]; + + beginTest ("Check feature disablement"); + { + Font baseFont { FontOptions { typeface }.withPointHeight (22) + .withFeatureDisabled ("aalt") }; + + auto ga = makeGlyphArrangement ("AD", baseFont); + expectEquals (ga.getGlyph (0).getGlyphIndex(), aIndex); + expectEquals (ga.getGlyph (1).getGlyphIndex(), dIndex); + } + + beginTest ("Check feature enablement"); + { + Font aaltFont { FontOptions { typeface }.withPointHeight (22) + .withFeatureEnabled ("aalt") }; + + auto ga = makeGlyphArrangement ("AD", aaltFont); + expectEquals (ga.getGlyph (0).getGlyphIndex(), aIndex); + expectEquals (ga.getGlyph (1).getGlyphIndex(), aIndex); + } + } + +private: + static GlyphArrangement makeGlyphArrangement (const String& text, const Font& font) + { + GlyphArrangement ga; + ga.addLineOfText (font, text, 0, 0); + return ga; + } + + static MemoryBlock unpackFontData() + { + // This is a very simple font with glyphs for 'A', 'D'. + // It also has a single GSUB feature (aalt) that will substitute 'D' with 'A' when enabled. + static constexpr uint8_t testFontZip[] = + { + 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x9f, + 0xb0, 0x5a, 0xda, 0xa4, 0x50, 0xbd, 0x61, 0x02, 0x00, 0x00, 0x50, 0x04, + 0x00, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6f, + 0x74, 0x66, 0x55, 0x54, 0x09, 0x00, 0x03, 0x50, 0x8a, 0x27, 0x68, 0x52, + 0x8a, 0x27, 0x68, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xf5, 0x01, 0x00, + 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0xa5, 0x53, 0xcf, 0x6b, 0x13, 0x41, + 0x14, 0xfe, 0x66, 0x67, 0x37, 0x89, 0x35, 0xa6, 0xb1, 0x3f, 0x24, 0x05, + 0x29, 0x41, 0x22, 0x55, 0x69, 0x63, 0xda, 0x83, 0x94, 0x58, 0x51, 0xd3, + 0x34, 0x7a, 0xa8, 0xc4, 0xb6, 0xb1, 0x14, 0x29, 0xe8, 0xea, 0x6e, 0x9b, + 0x40, 0x7e, 0x99, 0x6c, 0x68, 0x0f, 0x22, 0x82, 0xa0, 0x42, 0x2e, 0xe2, + 0x41, 0x0f, 0x7a, 0xf5, 0xe6, 0xc9, 0x93, 0x7a, 0x11, 0x2f, 0xa2, 0x37, + 0xff, 0x88, 0x1c, 0x04, 0x0b, 0x2b, 0x52, 0x69, 0x28, 0xd2, 0xf5, 0xcd, + 0x66, 0x12, 0x92, 0x5e, 0x7a, 0xf0, 0xc1, 0xee, 0xfb, 0xde, 0x37, 0x33, + 0xdf, 0xcc, 0xbc, 0xf7, 0x26, 0x9d, 0xc9, 0xa4, 0xd1, 0x87, 0x87, 0xe0, + 0x08, 0xcf, 0xa6, 0x52, 0xe1, 0xab, 0x8f, 0xdf, 0x6d, 0x00, 0x3c, 0x09, + 0xe0, 0xfd, 0x95, 0xa5, 0x1b, 0x89, 0x37, 0xbf, 0x3f, 0x70, 0x8a, 0xff, + 0x52, 0x9c, 0x4c, 0x2f, 0x9d, 0x9d, 0x9a, 0x9b, 0x9e, 0x1b, 0x01, 0x58, + 0x90, 0xe2, 0xdb, 0x77, 0x0b, 0x7a, 0x19, 0x37, 0xf1, 0x1a, 0x50, 0x7e, + 0x50, 0x3c, 0x93, 0x35, 0x75, 0xe3, 0x8c, 0xef, 0x63, 0x83, 0xf0, 0x5b, + 0xfa, 0xce, 0x65, 0x89, 0xd0, 0x9e, 0xb3, 0x18, 0x61, 0xc1, 0x45, 0xb2, + 0x05, 0x6b, 0x53, 0x1b, 0x62, 0xe3, 0x80, 0x7a, 0x89, 0xe2, 0x40, 0x41, + 0xdf, 0x2c, 0x43, 0xbd, 0x4e, 0x90, 0xf9, 0xe8, 0xe7, 0x29, 0xea, 0x05, + 0xf3, 0xd7, 0xb7, 0x07, 0x29, 0x8a, 0xcb, 0xf4, 0x6d, 0x94, 0x4b, 0x55, + 0x8b, 0xce, 0x45, 0xc6, 0x23, 0xf4, 0x0b, 0x83, 0x89, 0xa9, 0xc0, 0xd6, + 0x3d, 0xcf, 0x8b, 0x5b, 0xfd, 0x33, 0x7f, 0xc0, 0x55, 0x31, 0x88, 0xc6, + 0xb5, 0xf8, 0xf7, 0x8e, 0x37, 0x60, 0xb0, 0x6d, 0x2e, 0xce, 0xc3, 0xa1, + 0xa0, 0x65, 0xb4, 0x86, 0x4f, 0xe3, 0x2b, 0x9d, 0x73, 0x05, 0xc6, 0x9e, + 0x9f, 0x6d, 0xbb, 0x4a, 0xdd, 0x46, 0xbb, 0xd0, 0x39, 0x84, 0x1c, 0x57, + 0x56, 0xd8, 0x33, 0x68, 0x50, 0x71, 0xb0, 0xf5, 0xa8, 0x5c, 0x24, 0x23, + 0x77, 0x19, 0x49, 0xda, 0x0b, 0xed, 0x1d, 0x7b, 0xac, 0x75, 0x83, 0x20, + 0xbe, 0xc8, 0x95, 0xcc, 0xdd, 0xa5, 0x85, 0x15, 0x78, 0x29, 0x62, 0xf2, + 0x34, 0x23, 0x38, 0x2c, 0xb1, 0x0a, 0x3f, 0x22, 0x12, 0x6b, 0xc4, 0xc6, + 0x24, 0xf6, 0x74, 0x78, 0xb5, 0x4b, 0x47, 0x95, 0x3a, 0x1c, 0x4c, 0x3d, + 0x44, 0x8c, 0x0f, 0xe7, 0x25, 0x56, 0x10, 0xc0, 0xac, 0xc4, 0x1c, 0x53, + 0x58, 0x90, 0x58, 0x45, 0x08, 0x8f, 0x24, 0xd6, 0x70, 0x0c, 0xaf, 0x24, + 0xf6, 0x74, 0xf8, 0xc1, 0x2e, 0x9d, 0xc1, 0x96, 0x4e, 0xc6, 0xac, 0x5a, + 0x8b, 0xe6, 0x7a, 0x2d, 0xaf, 0x57, 0xd6, 0x4a, 0x45, 0x2b, 0x51, 0xcb, + 0xe5, 0x0d, 0xb3, 0x12, 0x0f, 0x0b, 0x3e, 0x2a, 0x07, 0x04, 0x9e, 0x90, + 0x78, 0xd9, 0xac, 0x54, 0x73, 0xa5, 0x62, 0x38, 0x16, 0x9d, 0x44, 0x06, + 0x26, 0xaa, 0xb0, 0xb0, 0x48, 0x7e, 0x1d, 0x35, 0xe4, 0xa1, 0xa3, 0x82, + 0x35, 0x94, 0x50, 0x24, 0x36, 0x41, 0x4c, 0x8e, 0x38, 0x83, 0x46, 0x2b, + 0x88, 0x53, 0xca, 0xda, 0xf3, 0xa3, 0xfb, 0x56, 0xb4, 0xf9, 0x89, 0x7d, + 0xfc, 0xb2, 0xbb, 0xb2, 0x4a, 0x2a, 0x42, 0x31, 0x4c, 0x09, 0x8b, 0x62, + 0xb2, 0x95, 0x61, 0xb7, 0xce, 0x18, 0x12, 0x37, 0x71, 0xbd, 0x8a, 0x53, + 0x6e, 0x26, 0x45, 0x06, 0x15, 0x51, 0x39, 0xc7, 0x69, 0x55, 0xd0, 0x71, + 0x9c, 0xcf, 0xce, 0xa7, 0x4e, 0x7d, 0xf9, 0x41, 0x6d, 0xa0, 0x32, 0x30, + 0xc6, 0x8e, 0x74, 0x5f, 0x59, 0x10, 0x63, 0xcd, 0xe3, 0xca, 0xe8, 0x4b, + 0xf3, 0xce, 0x6a, 0xd0, 0xa9, 0xd7, 0x3b, 0xc0, 0xef, 0xb5, 0xed, 0xe6, + 0xd3, 0xdd, 0xfb, 0x5a, 0xa3, 0xbf, 0xbe, 0x33, 0x3f, 0xf0, 0xf3, 0x28, + 0x14, 0xc6, 0x3c, 0x03, 0xd1, 0x62, 0x2d, 0x9f, 0xef, 0x51, 0x20, 0xe5, + 0x27, 0x38, 0x81, 0x93, 0x24, 0xcf, 0x86, 0x4f, 0x5f, 0x58, 0x68, 0x6e, + 0xd9, 0xf6, 0xf0, 0x6e, 0xc8, 0x6b, 0x8f, 0xed, 0xac, 0xd6, 0x6d, 0xdb, + 0xb7, 0x17, 0xf2, 0x06, 0xfe, 0x87, 0x73, 0xef, 0xd7, 0x87, 0x51, 0x8c, + 0x83, 0x25, 0x53, 0xf3, 0x19, 0xaa, 0xb2, 0xdb, 0xf5, 0x94, 0x05, 0xf7, + 0xa5, 0xe9, 0x7a, 0xde, 0x82, 0x4f, 0x76, 0x16, 0x93, 0xdd, 0x29, 0x7a, + 0x81, 0xc1, 0xe3, 0xce, 0x61, 0xe2, 0xb5, 0x50, 0xa9, 0x00, 0xc3, 0xf5, + 0x84, 0xfe, 0x01, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x14, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x08, 0x9f, 0xb0, 0x5a, 0xda, 0xa4, 0x50, 0xbd, 0x61, + 0x02, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, + 0x00, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6f, 0x74, 0x66, 0x55, 0x54, 0x05, + 0x00, 0x03, 0x50, 0x8a, 0x27, 0x68, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, + 0xf5, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x4e, 0x00, 0x00, + 0x00, 0xa3, 0x02, 0x00, 0x00, 0x00, 0x00 + }; + + MemoryInputStream memoryStream { (const void*) testFontZip, std::size (testFontZip), false }; + ZipFile zip { &memoryStream, false }; + + auto stream = rawToUniquePtr (zip.createStreamForEntry (zip.getIndexOfFileName ("test.otf"))); + jassert (stream != nullptr); + + MemoryBlock data; + stream->readIntoMemoryBlock (data); + + return data; + } +}; + +static FontFeatureTests fontFeatureTests; + #endif } // namespace juce diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index 9dae78116a..afba6281a4 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -384,6 +384,16 @@ public: */ static Typeface::Ptr findSystemTypeface(); + /** Returns the OpenType features supported by this typeface. + + This method returns a list of all OpenType font features (such as ligatures, + small caps, stylistic alternates, etc.) that are available in the current + typeface. + + @see FontFeatureTag, FontFeatureSetting, FontOptions, Font + */ + std::vector getSupportedFeatures() const; + private: //============================================================================== String name; diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp index 80db67eba0..be60a39344 100644 --- a/modules/juce_graphics/juce_graphics.cpp +++ b/modules/juce_graphics/juce_graphics.cpp @@ -193,6 +193,7 @@ extern "C" #include "image_formats/juce_PNGLoader.cpp" #include "fonts/juce_AttributedString.cpp" #include "fonts/juce_Typeface.cpp" +#include "fonts/juce_FontFeatures.cpp" #include "fonts/juce_FontOptions.cpp" #include "fonts/juce_Font.cpp" #include "detail/juce_Ranges.cpp" diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index bba6767e1d..509336c14d 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -135,6 +135,7 @@ namespace juce #include "contexts/juce_GraphicsContext.h" #include "images/juce_Image.h" #include "colour/juce_FillType.h" +#include "fonts/juce_FontFeatures.h" #include "fonts/juce_Typeface.h" #include "fonts/juce_FontOptions.h" #include "fonts/juce_Font.h"