GlyphSubstitution.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. //MIT, 2016-present, WinterDev
  2. using System;
  3. using System.Collections.Generic;
  4. using Typography.OpenFont;
  5. using Typography.OpenFont.Tables;
  6. namespace Typography.TextLayout
  7. {
  8. /// <summary>
  9. /// gsub lookup context
  10. /// </summary>
  11. class GSubLkContext
  12. {
  13. public readonly GSUB.LookupTable lookup;
  14. public GSubLkContextName ContextName;
  15. #if DEBUG
  16. public string dbugFeatureName;
  17. #endif
  18. public GSubLkContext(GSUB.LookupTable lookup)
  19. {
  20. this.lookup = lookup;
  21. }
  22. int _glyphCount;
  23. public void SetGlyphCount(int glyphCount)
  24. {
  25. _glyphCount = glyphCount;
  26. }
  27. public bool WillCheckThisGlyph(int pos)
  28. {
  29. switch (ContextName)
  30. {
  31. default: return true;
  32. case GSubLkContextName.Init: return _glyphCount > 1 && pos == 0; //the first one
  33. case GSubLkContextName.Medi: return _glyphCount > 2 && (pos > 0 && pos < _glyphCount); //in between
  34. case GSubLkContextName.Fina: return _glyphCount > 1 && pos == _glyphCount - 1;//the last one
  35. }
  36. }
  37. }
  38. //TODO: review here again
  39. enum GSubLkContextName : byte
  40. {
  41. None,
  42. Fina, //"fina"
  43. Init, //"init"
  44. Medi //"medi"
  45. }
  46. static class KnownLayoutTags
  47. {
  48. static readonly Dictionary<string, bool> s_knownGSubTags = new Dictionary<string, bool>();
  49. static KnownLayoutTags()
  50. {
  51. CollectTags(s_knownGSubTags, "ccmp");
  52. //arabic-related
  53. CollectTags(s_knownGSubTags, "liga,dlig,falt,rclt,rlig,locl,init,medi,fina,isol");
  54. //math-glyph related
  55. CollectTags(s_knownGSubTags, "math,ssty,dlts,flac");
  56. //indic script related
  57. CollectTags(s_knownGSubTags, "abvs,akhn,blwf,blws,cjct,half,haln,nukt,pres,psts,rkrf,rphf");
  58. }
  59. public static bool IsKnownGSUB_Tags(string tagName) => s_knownGSubTags.ContainsKey(tagName);
  60. static void CollectTags(Dictionary<string, bool> dic, string tags_str)
  61. {
  62. string[] tags = tags_str.Split(',');
  63. for (int i = 0; i < tags.Length; ++i)
  64. {
  65. dic[tags[i].Trim()] = true;//replace
  66. }
  67. }
  68. }
  69. /// <summary>
  70. /// glyph substitution manager
  71. /// </summary>
  72. class GlyphSubstitution
  73. {
  74. bool _enableLigation = true; // enable by default
  75. bool _enableComposition = true;
  76. bool _mustRebuildTables = true;
  77. bool _enableMathFeature = true;
  78. readonly Typeface _typeface;
  79. public GlyphSubstitution(Typeface typeface, uint scriptTag, uint langTag)
  80. {
  81. ScriptTag = scriptTag;
  82. LangTag = langTag;
  83. _typeface = typeface;
  84. _mustRebuildTables = true;
  85. }
  86. #if DEBUG
  87. public string dbugScriptLang;
  88. #endif
  89. public void DoSubstitution(IGlyphIndexList glyphIndexList)
  90. {
  91. // Rebuild tables if configuration changed
  92. if (_mustRebuildTables)
  93. {
  94. RebuildTables();
  95. _mustRebuildTables = false;
  96. }
  97. // Iterate over lookups, then over glyphs, as explained in the spec:
  98. // "During text processing, a client applies a lookup to each glyph
  99. // in the string before moving to the next lookup."
  100. // https://www.microsoft.com/typography/otspec/gsub.htm
  101. foreach (GSubLkContext lookupCtx in _lookupTables)
  102. {
  103. GSUB.LookupTable lookupTable = lookupCtx.lookup;
  104. lookupCtx.SetGlyphCount(glyphIndexList.Count);
  105. for (int pos = 0; pos < glyphIndexList.Count; ++pos)
  106. {
  107. if (!lookupCtx.WillCheckThisGlyph(pos))
  108. {
  109. continue;
  110. }
  111. lookupTable.DoSubstitutionAt(glyphIndexList, pos, glyphIndexList.Count - pos);
  112. }
  113. }
  114. }
  115. public uint ScriptTag { get; }
  116. public uint LangTag { get; }
  117. /// <summary>
  118. /// enable GSUB type 4, ligation (liga)
  119. /// </summary>
  120. public bool EnableLigation
  121. {
  122. get => _enableLigation;
  123. set
  124. {
  125. if (value != _enableLigation)
  126. { //test change before accept value
  127. _mustRebuildTables = true;
  128. }
  129. _enableLigation = value;
  130. }
  131. }
  132. /// <summary>
  133. /// enable GSUB glyph composition (ccmp)
  134. /// </summary>
  135. public bool EnableComposition
  136. {
  137. get => _enableComposition;
  138. set
  139. {
  140. if (value != _enableComposition)
  141. {
  142. //test change before accept value
  143. _mustRebuildTables = true;
  144. }
  145. _enableComposition = value;
  146. }
  147. }
  148. public bool EnableMathFeature
  149. {
  150. get => _enableMathFeature;
  151. set
  152. {
  153. if (value != _enableMathFeature)
  154. {
  155. _mustRebuildTables = true;
  156. }
  157. _enableMathFeature = value;
  158. }
  159. }
  160. internal List<GSubLkContext> _lookupTables = new List<GSubLkContext>();
  161. internal void RebuildTables()
  162. {
  163. _lookupTables.Clear();
  164. // check if this lang has
  165. GSUB gsubTable = _typeface.GSUBTable;
  166. ScriptTable scriptTable = gsubTable.ScriptList[ScriptTag];
  167. if (scriptTable == null) return;
  168. //-------
  169. ScriptTable.LangSysTable selectedLang = null;
  170. if (LangTag == 0)
  171. {
  172. //use default
  173. selectedLang = scriptTable.defaultLang;
  174. if (selectedLang == null && scriptTable.langSysTables != null && scriptTable.langSysTables.Length > 0)
  175. {
  176. //some font not defult lang
  177. //so we use it from langSysTable
  178. //find selected lang,
  179. //if not => choose default
  180. selectedLang = scriptTable.langSysTables[0];
  181. }
  182. }
  183. else
  184. {
  185. if (LangTag == scriptTable.defaultLang.langSysTagIden)
  186. {
  187. //found
  188. selectedLang = scriptTable.defaultLang;
  189. }
  190. if (scriptTable.langSysTables != null && scriptTable.langSysTables.Length > 0)
  191. { //find selected lang,
  192. //if not => choose default
  193. for (int i = 0; i < scriptTable.langSysTables.Length; ++i)
  194. {
  195. ScriptTable.LangSysTable s = scriptTable.langSysTables[i];
  196. if (s.langSysTagIden == LangTag)
  197. {
  198. //found
  199. selectedLang = s;
  200. break;
  201. }
  202. }
  203. }
  204. }
  205. //-----------------------------------
  206. //some lang need special management
  207. //TODO: review here again
  208. #if DEBUG
  209. if (selectedLang == null)
  210. {
  211. //TODO:...
  212. throw new NotSupportedException();
  213. }
  214. if (selectedLang.HasRequireFeature)
  215. {
  216. // TODO: review here
  217. }
  218. #endif
  219. if (selectedLang.featureIndexList == null)
  220. {
  221. return;
  222. }
  223. //(one lang may has many features)
  224. //Enumerate features we want and add the corresponding lookup tables
  225. foreach (ushort featureIndex in selectedLang.featureIndexList)
  226. {
  227. FeatureList.FeatureTable feature = gsubTable.FeatureList.featureTables[featureIndex];
  228. bool includeThisFeature = false;
  229. GSubLkContextName contextName = GSubLkContextName.None;
  230. switch (feature.TagName)
  231. {
  232. case "ccmp": // glyph composition/decomposition
  233. includeThisFeature = EnableComposition;
  234. break;
  235. case "liga": // Standard Ligatures --enable by default
  236. includeThisFeature = EnableLigation;
  237. break;
  238. case "init":
  239. includeThisFeature = true;
  240. contextName = GSubLkContextName.Init;
  241. break;
  242. case "medi":
  243. includeThisFeature = true;
  244. contextName = GSubLkContextName.Medi;
  245. break;
  246. case "fina":
  247. //Replaces glyphs for characters that have applicable joining properties with an alternate form when occurring in a final context.
  248. //This applies to characters that have one of the following Unicode Joining_Type property
  249. includeThisFeature = true;
  250. contextName = GSubLkContextName.Fina;
  251. break;
  252. default:
  253. {
  254. //other, TODO review here
  255. includeThisFeature = true;
  256. if (!KnownLayoutTags.IsKnownGSUB_Tags(feature.TagName))
  257. {
  258. includeThisFeature = false;
  259. #if DEBUG
  260. System.Diagnostics.Debug.WriteLine("gsub_skip_feature_tag:" + feature.TagName);
  261. #endif
  262. }
  263. else
  264. {
  265. }
  266. }
  267. break;
  268. }
  269. if (includeThisFeature)
  270. {
  271. foreach (ushort lookupIndex in feature.LookupListIndices)
  272. {
  273. var gsubcontext = new GSubLkContext(gsubTable.LookupList[lookupIndex]) { ContextName = contextName };
  274. #if DEBUG
  275. gsubcontext.dbugFeatureName = feature.TagName;
  276. #endif
  277. _lookupTables.Add(gsubcontext);
  278. }
  279. }
  280. }
  281. }
  282. /// <summary>
  283. /// collect all associate glyph index of specific input lang
  284. /// </summary>
  285. /// <param name="outputGlyphIndex"></param>
  286. public void CollectAdditionalSubstitutionGlyphIndices(List<ushort> outputGlyphIndices)
  287. {
  288. if (_mustRebuildTables)
  289. {
  290. RebuildTables();
  291. _mustRebuildTables = false;
  292. }
  293. //-------------
  294. //add some glyphs that also need by substitution process
  295. foreach (GSubLkContext subLkctx in _lookupTables)
  296. {
  297. subLkctx.lookup.CollectAssociatedSubstitutionGlyph(outputGlyphIndices);
  298. }
  299. //
  300. //WARN :not ensure glyph unique at this stage
  301. //please do it in later state
  302. }
  303. }
  304. }
  305. namespace Typography.OpenFont.Extensions
  306. {
  307. public static class TypefaceExtension5
  308. {
  309. public static void CollectAdditionalGlyphIndices(this Typeface typeface, List<ushort> outputGlyphs, ScriptLang scLang)
  310. {
  311. if (typeface.GSUBTable != null)
  312. {
  313. (new Typography.TextLayout.GlyphSubstitution(typeface, scLang.scriptTag, scLang.sysLangTag)).CollectAdditionalSubstitutionGlyphIndices(outputGlyphs);
  314. }
  315. }
  316. }
  317. }