@@ -921,29 +921,29 @@ public string GetFeatureValueLabel(int featureId, int valueId, int languageId)
921921
922922 private sealed class OpenTypeFontFeatureProvider : IFontFeatureProvider
923923 {
924- private static readonly Dictionary < string , string > s_featureLabels = new Dictionary < string , string >
925- {
926- { "aalt" , "Access All Alternates " } ,
927- { "c2sc" , "Small Capitals From Capitals " } ,
928- { "calt" , "Contextual Alternates " } ,
929- { "case" , "Case-Sensitive Forms " } ,
930- { "ccmp" , "Glyph Composition/Decomposition " } ,
931- { "clig" , "Contextual Ligatures " } ,
932- { "dlig" , "Discretionary Ligatures " } ,
933- { "frac" , "Fractions " } ,
934- { "kern" , "Kerning " } ,
935- { "liga" , "Standard Ligatures " } ,
936- { "lnum" , "Lining Figures " } ,
937- { "onum" , "Oldstyle Figures " } ,
938- { "pnum" , "Proportional Figures " } ,
939- { "salt" , "Stylistic Alternates " } ,
940- { "smcp" , "Small Capitals " } ,
941- { "ss01" , "Stylistic Set 1 " } ,
942- { "ss02" , "Stylistic Set 2 " } ,
943- { "ss03" , "Stylistic Set 3 " } ,
944- { "ss04" , "Stylistic Set 4 " } ,
945- { "ss05" , "Stylistic Set 5 " } ,
946- { "tnum" , "Tabular Figures " } ,
924+ private static readonly Dictionary < string , string > s_featureLabelResourceIds = new Dictionary < string , string >
925+ {
926+ { "aalt" , "kstidOpenTypeFeature_aalt " } ,
927+ { "c2sc" , "kstidOpenTypeFeature_c2sc " } ,
928+ { "calt" , "kstidOpenTypeFeature_calt " } ,
929+ { "case" , "kstidOpenTypeFeature_case " } ,
930+ { "ccmp" , "kstidOpenTypeFeature_ccmp " } ,
931+ { "clig" , "kstidOpenTypeFeature_clig " } ,
932+ { "dlig" , "kstidOpenTypeFeature_dlig " } ,
933+ { "frac" , "kstidOpenTypeFeature_frac " } ,
934+ { "kern" , "kstidOpenTypeFeature_kern " } ,
935+ { "liga" , "kstidOpenTypeFeature_liga " } ,
936+ { "lnum" , "kstidOpenTypeFeature_lnum " } ,
937+ { "onum" , "kstidOpenTypeFeature_onum " } ,
938+ { "pnum" , "kstidOpenTypeFeature_pnum " } ,
939+ { "salt" , "kstidOpenTypeFeature_salt " } ,
940+ { "smcp" , "kstidOpenTypeFeature_smcp " } ,
941+ { "ss01" , "kstidOpenTypeFeature_ss01 " } ,
942+ { "ss02" , "kstidOpenTypeFeature_ss02 " } ,
943+ { "ss03" , "kstidOpenTypeFeature_ss03 " } ,
944+ { "ss04" , "kstidOpenTypeFeature_ss04 " } ,
945+ { "ss05" , "kstidOpenTypeFeature_ss05 " } ,
946+ { "tnum" , "kstidOpenTypeFeature_tnum " } ,
947947 } ;
948948
949949 private readonly int [ ] m_featureIds ;
@@ -977,8 +977,14 @@ public string GetFeatureTag(int featureId)
977977 public string GetFeatureLabel ( int featureId , int languageId )
978978 {
979979 var tag = GetFeatureTag ( featureId ) ;
980- string label ;
981- return s_featureLabels . TryGetValue ( tag , out label ) ? label : tag ;
980+ string resourceId ;
981+ if ( s_featureLabelResourceIds . TryGetValue ( tag , out resourceId ) )
982+ {
983+ var label = FwCoreDlgControls . ResourceManager . GetString ( resourceId , CultureInfo . CurrentUICulture ) ;
984+ if ( ! string . IsNullOrEmpty ( label ) )
985+ return label ;
986+ }
987+ return tag ;
982988 }
983989
984990 public int [ ] GetFeatureValues ( int featureId , int maxValues , out int valueCount , out int defaultValue )
@@ -990,7 +996,9 @@ public int[] GetFeatureValues(int featureId, int maxValues, out int valueCount,
990996
991997 public string GetFeatureValueLabel ( int featureId , int valueId , int languageId )
992998 {
993- return valueId == 0 ? "Off" : "On" ;
999+ return valueId == 0
1000+ ? FwCoreDlgControls . ResourceManager . GetString ( "kstidOpenTypeFeatureValueOff" , CultureInfo . CurrentUICulture )
1001+ : FwCoreDlgControls . ResourceManager . GetString ( "kstidOpenTypeFeatureValueOn" , CultureInfo . CurrentUICulture ) ;
9941002 }
9951003 }
9961004
@@ -1009,24 +1017,88 @@ internal static string ConvertRendererNeutralFeatureStringToIds(string fontFeatu
10091017 setting . Value ) ) ) ;
10101018 }
10111019
1012- private static class OpenTypeFontFeatureReader
1020+ internal static class OpenTypeFontFeatureReader
10131021 {
10141022 private const uint GdiError = 0xFFFFFFFF ;
1023+ private const int MaxCacheEntries = 32 ;
1024+ private const int ObjFont = 6 ;
10151025 private static readonly uint [ ] s_layoutTables = { MakeTableTag ( "GSUB" ) , MakeTableTag ( "GPOS" ) } ;
1026+ private static readonly object s_cacheLock = new object ( ) ;
1027+ private static readonly Dictionary < FontFeatureCacheKey , string [ ] > s_featureTagCache =
1028+ new Dictionary < FontFeatureCacheKey , string [ ] > ( ) ;
1029+ private static readonly Queue < FontFeatureCacheKey > s_cacheOrder = new Queue < FontFeatureCacheKey > ( ) ;
1030+ private static Func < IntPtr , uint , byte [ ] > s_tableReader = ReadTable ;
10161031
10171032 [ DllImport ( "gdi32.dll" , SetLastError = true ) ]
10181033 private static extern uint GetFontData ( IntPtr hdc , uint table , uint offset , byte [ ] buffer , uint length ) ;
10191034
1035+ [ DllImport ( "gdi32.dll" ) ]
1036+ private static extern IntPtr GetCurrentObject ( IntPtr hdc , int objectType ) ;
1037+
1038+ [ DllImport ( "gdi32.dll" , CharSet = CharSet . Unicode ) ]
1039+ private static extern int GetObject ( IntPtr hObject , int size , ref LogFont logFont ) ;
1040+
10201041 public static IReadOnlyList < string > GetFeatureTags ( IntPtr hdc )
10211042 {
1043+ var cacheKey = FontFeatureCacheKey . FromHdc ( hdc ) ;
1044+ lock ( s_cacheLock )
1045+ {
1046+ string [ ] cachedTags ;
1047+ if ( s_featureTagCache . TryGetValue ( cacheKey , out cachedTags ) )
1048+ return cachedTags . ToArray ( ) ;
1049+ }
1050+
10221051 var tags = new SortedSet < string > ( StringComparer . Ordinal ) ;
10231052 foreach ( var table in s_layoutTables )
10241053 {
1025- var tableData = ReadTable ( hdc , table ) ;
1054+ var tableData = s_tableReader ( hdc , table ) ;
10261055 if ( tableData != null )
10271056 ReadFeatureList ( tableData , tags ) ;
10281057 }
1029- return tags . ToArray ( ) ;
1058+ var discoveredTags = tags . ToArray ( ) ;
1059+ lock ( s_cacheLock )
1060+ {
1061+ if ( ! s_featureTagCache . ContainsKey ( cacheKey ) )
1062+ {
1063+ if ( s_cacheOrder . Count >= MaxCacheEntries )
1064+ s_featureTagCache . Remove ( s_cacheOrder . Dequeue ( ) ) ;
1065+ s_featureTagCache [ cacheKey ] = discoveredTags ;
1066+ s_cacheOrder . Enqueue ( cacheKey ) ;
1067+ }
1068+ }
1069+ return discoveredTags . ToArray ( ) ;
1070+ }
1071+
1072+ internal static void ClearCacheForTests ( )
1073+ {
1074+ lock ( s_cacheLock )
1075+ {
1076+ ClearCache ( ) ;
1077+ }
1078+ }
1079+
1080+ internal static IDisposable UseTableReaderForTests ( Func < IntPtr , uint , byte [ ] > tableReader )
1081+ {
1082+ lock ( s_cacheLock )
1083+ {
1084+ var previousReader = s_tableReader ;
1085+ s_tableReader = tableReader ?? ReadTable ;
1086+ ClearCache ( ) ;
1087+ return new DisposableAction ( ( ) =>
1088+ {
1089+ lock ( s_cacheLock )
1090+ {
1091+ s_tableReader = previousReader ;
1092+ ClearCache ( ) ;
1093+ }
1094+ } ) ;
1095+ }
1096+ }
1097+
1098+ private static void ClearCache ( )
1099+ {
1100+ s_featureTagCache . Clear ( ) ;
1101+ s_cacheOrder . Clear ( ) ;
10301102 }
10311103
10321104 private static byte [ ] ReadTable ( IntPtr hdc , uint table )
@@ -1072,6 +1144,118 @@ private static uint MakeTableTag(string tag)
10721144 {
10731145 return ( uint ) ( tag [ 0 ] | tag [ 1 ] << 8 | tag [ 2 ] << 16 | tag [ 3 ] << 24 ) ;
10741146 }
1147+
1148+ [ StructLayout ( LayoutKind . Sequential , CharSet = System . Runtime . InteropServices . CharSet . Unicode ) ]
1149+ private struct LogFont
1150+ {
1151+ public int Height ;
1152+ public int Width ;
1153+ public int Escapement ;
1154+ public int Orientation ;
1155+ public int Weight ;
1156+ public byte Italic ;
1157+ public byte Underline ;
1158+ public byte StrikeOut ;
1159+ public byte CharSet ;
1160+ public byte OutPrecision ;
1161+ public byte ClipPrecision ;
1162+ public byte Quality ;
1163+ public byte PitchAndFamily ;
1164+ [ MarshalAs ( UnmanagedType . ByValTStr , SizeConst = 32 ) ]
1165+ public string FaceName ;
1166+ }
1167+
1168+ private struct FontFeatureCacheKey : IEquatable < FontFeatureCacheKey >
1169+ {
1170+ private readonly string m_faceName ;
1171+ private readonly int m_height ;
1172+ private readonly int m_weight ;
1173+ private readonly byte m_italic ;
1174+ private readonly byte m_charSet ;
1175+ private readonly byte m_pitchAndFamily ;
1176+ private readonly IntPtr m_fallbackHdc ;
1177+
1178+ private FontFeatureCacheKey ( string faceName , int height , int weight , byte italic ,
1179+ byte charSet , byte pitchAndFamily , IntPtr fallbackHdc )
1180+ {
1181+ m_faceName = faceName ?? string . Empty ;
1182+ m_height = height ;
1183+ m_weight = weight ;
1184+ m_italic = italic ;
1185+ m_charSet = charSet ;
1186+ m_pitchAndFamily = pitchAndFamily ;
1187+ m_fallbackHdc = fallbackHdc ;
1188+ }
1189+
1190+ public static FontFeatureCacheKey FromHdc ( IntPtr hdc )
1191+ {
1192+ if ( hdc != IntPtr . Zero )
1193+ {
1194+ var hfont = GetCurrentObject ( hdc , ObjFont ) ;
1195+ if ( hfont != IntPtr . Zero )
1196+ {
1197+ var logFont = new LogFont ( ) ;
1198+ if ( GetObject ( hfont , Marshal . SizeOf ( typeof ( LogFont ) ) , ref logFont ) > 0 )
1199+ {
1200+ return new FontFeatureCacheKey ( logFont . FaceName , logFont . Height ,
1201+ logFont . Weight , logFont . Italic , logFont . CharSet ,
1202+ logFont . PitchAndFamily , IntPtr . Zero ) ;
1203+ }
1204+ }
1205+ }
1206+ return new FontFeatureCacheKey ( string . Empty , 0 , 0 , 0 , 0 , 0 , hdc ) ;
1207+ }
1208+
1209+ public bool Equals ( FontFeatureCacheKey other )
1210+ {
1211+ return string . Equals ( m_faceName , other . m_faceName , StringComparison . OrdinalIgnoreCase ) &&
1212+ m_height == other . m_height &&
1213+ m_weight == other . m_weight &&
1214+ m_italic == other . m_italic &&
1215+ m_charSet == other . m_charSet &&
1216+ m_pitchAndFamily == other . m_pitchAndFamily &&
1217+ m_fallbackHdc == other . m_fallbackHdc ;
1218+ }
1219+
1220+ public override bool Equals ( object obj )
1221+ {
1222+ return obj is FontFeatureCacheKey && Equals ( ( FontFeatureCacheKey ) obj ) ;
1223+ }
1224+
1225+ public override int GetHashCode ( )
1226+ {
1227+ unchecked
1228+ {
1229+ var hash = StringComparer . OrdinalIgnoreCase . GetHashCode ( m_faceName ) ;
1230+ hash = ( hash * 397 ) ^ m_height ;
1231+ hash = ( hash * 397 ) ^ m_weight ;
1232+ hash = ( hash * 397 ) ^ m_italic ;
1233+ hash = ( hash * 397 ) ^ m_charSet ;
1234+ hash = ( hash * 397 ) ^ m_pitchAndFamily ;
1235+ hash = ( hash * 397 ) ^ m_fallbackHdc . GetHashCode ( ) ;
1236+ return hash ;
1237+ }
1238+ }
1239+ }
1240+
1241+ private sealed class DisposableAction : IDisposable
1242+ {
1243+ private Action m_disposeAction ;
1244+
1245+ public DisposableAction ( Action disposeAction )
1246+ {
1247+ m_disposeAction = disposeAction ;
1248+ }
1249+
1250+ public void Dispose ( )
1251+ {
1252+ var disposeAction = m_disposeAction ;
1253+ if ( disposeAction == null )
1254+ return ;
1255+ m_disposeAction = null ;
1256+ disposeAction ( ) ;
1257+ }
1258+ }
10751259 }
10761260 }
10771261}
0 commit comments