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"